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..ec26a1ac1152 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: "weekly" + 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..35f6aba3f267 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,47 +8,62 @@ 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + # 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 + # - 23 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@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0 with: - java-version: ${{ matrix.java }} - distribution: 'zulu' + java-version: | + ${{ matrix.java }} + 23 + 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: 'Integration Test' + if: matrix.java == 11 + shell: bash + run: util/gradle_integration_tests.sh publish_snapshot: name: 'Publish snapshot' @@ -57,22 +72,18 @@ 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: 'Set up JDKs' + uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0 with: - java-version: 11 - distribution: 'zulu' + # 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: 23 + distribution: 'temurin' server-id: sonatype-nexus-snapshots server-username: CI_DEPLOY_USERNAME server-password: CI_DEPLOY_PASSWORD + cache: 'maven' - name: 'Publish' env: CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }} @@ -80,25 +91,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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: 'Set up JDKs' + uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.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 + 23 + distribution: 'temurin' + cache: 'maven' - name: 'Generate latest docs' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml new file mode 100644 index 000000000000..44e5c1a69295 --- /dev/null +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -0,0 +1,13 @@ +name: "Validate Gradle Wrapper" +on: [push, pull_request] + +permissions: + contents: read + +jobs: + validation: + name: "Validation" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: gradle/wrapper-validation-action@f9c9c575b8b21b6485636a91ffecd10e558c62f6 # v3.5.0 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 000000000000..159ca553f9de --- /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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + 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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.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@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 + 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/COPYING b/LICENSE similarity index 100% rename from COPYING rename to LICENSE diff --git a/README.md b/README.md index 57a8e455cd7e..a182a6b4f509 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.4.0-jre` or +`33.4.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.4.0-jre - 30.1.1-android + 33.4.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.4.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.4.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.4.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.4.0-android") } ``` @@ -68,13 +74,13 @@ 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. -- Snapshot API Docs: [guava][guava-snapshot-api-docs] -- Snapshot API Diffs: [guava][guava-snapshot-api-diffs] +[Snapshot API Javadoc][guava-snapshot-api-docs] as well as +[Snapshot API Diffs][guava-snapshot-api-diffs] are also available. ## 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 +88,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 +108,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.2`. It also has [some annotation-only dependencies][guava-deps], which we discuss in more detail at that link. @@ -113,10 +119,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 21 (Lollipop). [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-testlib/pom.xml b/android/guava-testlib/pom.xml index 79b05190ce85..b1575bc06144 100644 --- a/android/guava-testlib/pom.xml +++ b/android/guava-testlib/pom.xml @@ -15,12 +15,14 @@ - com.google.code.findbugs - jsr305 + org.jspecify + jspecify - org.checkerframework - checker-compat-qual + com.google.code.findbugs + jsr305 + 3.0.2 + test com.google.errorprone @@ -38,7 +40,8 @@ junit junit - compile + + 4.13.2 com.google.truth truth + ${truth.version} test + + + + com.google.guava + guava + + + + org.mvnsearch + toolchains-maven-plugin + + + maven-toolchains-plugin + maven-compiler-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..453ef743d267 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,8 +46,7 @@ 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( 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..baa8371cf10e 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,12 +237,10 @@ protected int getNullLocation() { return getNumElements() / 2; } - @SuppressWarnings("unchecked") protected MinimalCollection createDisjointCollection() { return MinimalCollection.of(e3(), e4()); } - @SuppressWarnings("unchecked") protected MinimalCollection emptyCollection() { return MinimalCollection.of(); } 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..cb599a32837d 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,6 +16,11 @@ 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; @@ -23,14 +28,14 @@ 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,7 +46,8 @@ * @author Chris Povirk */ @GwtCompatible -abstract class AbstractIteratorTester> { +@NullMarked +abstract class AbstractIteratorTester> { private Stimulus[] stimuli; private final Iterator elementsToInsert; private final Set features; @@ -57,7 +63,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 +71,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,16 +94,16 @@ 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); } } @@ -135,20 +141,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 +262,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 +274,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 +284,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 +320,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 +345,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 +359,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); + arraycopy(source, 0, copy, 0, size); + return asList(copy); } private interface IteratorOperation { - Object execute(Iterator iterator); + @Nullable Object execute(Iterator iterator); } /** @@ -371,16 +380,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 +405,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 +423,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 +454,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 +463,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,7 +471,7 @@ 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(); } }; @@ -475,7 +480,7 @@ private final IteratorOperation newAddMethod() { final 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); @@ -488,7 +493,7 @@ private final IteratorOperation newSetMethod() { final 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 +502,7 @@ public Object execute(Iterator iterator) { }; } - abstract static class Stimulus> { + abstract static class Stimulus> { private final String toString; protected Stimulus(String toString) { @@ -538,9 +543,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 +590,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..c220f30e21b3 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 @@ -30,12 +34,13 @@ * parameterize the test. * @author George van den Driessche */ -@GwtCompatible +@GwtCompatible(emulated = true) +@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); } + + /** + * 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..796363d05c17 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 @@ -18,6 +18,7 @@ import com.google.common.annotations.GwtCompatible; import java.io.Serializable; +import org.jspecify.annotations.Nullable; /** * Simple base class to verify that we handle generics correctly. @@ -38,7 +39,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) { 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..5f9aff4f1c5f 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,6 +57,8 @@ protected List createDerivedSuites( .named(getName() + " reserialized") .withFeatures(computeReserializedCollectionFeatures(parentBuilder.getFeatures())) .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); } return derivedSuites; 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..ed68a490d401 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, DerivedGenerator { 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/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..fe1ae83b5f74 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,24 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.entryComparator; +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 +44,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) +@NullMarked public class Helpers { // Clone of Objects.equal - static boolean equal(Object a, Object b) { + 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 +91,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 +107,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 +118,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 +128,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 +142,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 +156,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 +173,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,11 +199,11 @@ public static boolean addAll(Collection addTo, Iterable elem return modified; } - static Iterable reverse(final List list) { + static Iterable reverse(List list) { return new Iterable() { @Override public Iterator iterator() { - final ListIterator listIter = list.listIterator(list.size()); + ListIterator listIter = list.listIterator(list.size()); return new Iterator() { @Override public boolean hasNext() { @@ -213,7 +224,7 @@ public void remove() { }; } - static Iterator cycle(final Iterable iterable) { + static Iterator cycle(Iterable iterable) { return new Iterator() { Iterator iterator = Collections.emptySet().iterator(); @@ -237,30 +248,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 class EntryComparator + implements Comparator> { + final @Nullable Comparator keyComparator; + + public 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 +284,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 +304,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 +359,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() { + 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 +410,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 +430,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,29 +454,23 @@ 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) Comparable::compareTo; + sort(insertionOrder, entryComparator(keyComparator)); return insertionOrder; } @@ -444,7 +486,7 @@ public static Iterable> orderEntriesByKey( * 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 { + public abstract static class NullsBefore implements Comparator<@Nullable String>, Serializable { /* * We don't serialize this class in GWT, so we don't care about whether GWT will serialize this * field. @@ -460,7 +502,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 +526,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 +556,7 @@ private NullsBeforeTwo() { } } + @J2ktIncompatible @GwtIncompatible // reflection public static Method getMethod(Class clazz, String name) { try { 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..ac9e70cac1dc --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/IgnoreJRERequirement.java @@ -0,0 +1,31 @@ +/* + * 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.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}) +@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..ecb81dacf5e6 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 @@ -84,7 +86,9 @@ * @author Chris Povirk */ @GwtCompatible -public abstract class IteratorTester extends AbstractIteratorTester> { +@NullMarked +public abstract class IteratorTester + extends AbstractIteratorTester> { /** * Creates an IteratorTester. * 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..40de8ed16e8a 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,6 +114,8 @@ protected List createDerivedSuites( .named(getName() + " reserialized") .withFeatures(computeReserializedCollectionFeatures(parentBuilder.getFeatures())) .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); } return derivedSuites; 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..bbceb1ca278d 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 { @@ -149,7 +158,7 @@ protected final boolean supportsValuesHashCode(Map map) { for (V value : values) { if (value != null) { try { - value.hashCode(); + int unused = value.hashCode(); } catch (Exception e) { return false; } @@ -183,7 +192,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 +230,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 +274,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 +285,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 +318,8 @@ public void testContainsKey() { } public void testContainsValue() { - final Map map; - final V unmappedValue; + Map map; + V unmappedValue; try { map = makePopulatedMap(); unmappedValue = getValueNotInPopulatedMap(); @@ -334,8 +340,7 @@ public void testContainsValue() { } public void testEntrySet() { - final Map map; - final Set> entrySet; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -343,9 +348,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 +364,7 @@ public void testEntrySet() { } public void testEntrySetForEmptyMap() { - final Map map; + Map map; try { map = makeEmptyMap(); } catch (UnsupportedOperationException e) { @@ -368,9 +373,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 +383,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 +401,7 @@ public void testEntrySetContainsEntryNullKeyPresent() { if (!allowsNullKeys || !supportsPut) { return; } - final Map map; - final Set> entrySet; + Map map; try { map = makeEitherMap(); } catch (UnsupportedOperationException e) { @@ -405,8 +409,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 +418,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 +433,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 +467,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 +476,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 +499,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 +534,7 @@ public void testEntrySetRemoveMissingKey() { } public void testEntrySetRemoveDifferentValue() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -570,8 +564,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 +572,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 +583,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 +598,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 +619,7 @@ public void testEntrySetRemoveNullKeyMissing() { } public void testEntrySetRemoveAll() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -641,8 +634,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 +645,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 +660,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 +673,7 @@ public void testEntrySetRemoveAllNullFromEmpty() { } public void testEntrySetRetainAll() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -697,7 +681,10 @@ 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 didRemove = entrySet.retainAll(entriesToRetain); @@ -707,17 +694,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 +712,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 +726,7 @@ public void testEntrySetRetainAllNullFromEmpty() { } public void testEntrySetClear() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -755,22 +738,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 +757,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 +772,8 @@ public void testEntrySetSetValue() { return; } - final Map map; - final V valueToSet; + Map map; + V valueToSet; try { map = makePopulatedMap(); valueToSet = getValueNotInPopulatedMap(); @@ -804,8 +783,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 +798,7 @@ public void testEntrySetSetValueSameValue() { return; } - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -828,8 +807,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 +816,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 +836,8 @@ public void testEqualsForLargerMap() { return; } - final Map map; - final Map largerMap; + Map map; + Map largerMap; try { map = makePopulatedMap(); largerMap = makePopulatedMap(); @@ -874,8 +854,8 @@ public void testEqualsForSmallerMap() { return; } - final Map map; - final Map smallerMap; + Map map; + Map smallerMap; try { map = makePopulatedMap(); smallerMap = makePopulatedMap(); @@ -888,23 +868,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 +906,7 @@ public void testGet() { } public void testGetForEmptyMap() { - final Map map; + Map map; K unmappedKey = null; try { map = makeEmptyMap(); @@ -954,7 +935,7 @@ public void testGetNull() { } public void testHashCode() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -964,7 +945,7 @@ public void testHashCode() { } public void testHashCodeForEmptyMap() { - final Map map; + Map map; try { map = makeEmptyMap(); } catch (UnsupportedOperationException e) { @@ -974,9 +955,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 +973,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 +996,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 +1005,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 +1029,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 +1038,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 +1055,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 +1065,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 +1097,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 +1120,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 +1142,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 +1161,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 +1171,7 @@ public void testSize() { } public void testKeySetRemove() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1244,17 +1186,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 +1203,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 +1223,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 +1245,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 +1260,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 +1273,7 @@ public void testKeySetRemoveAllNullFromEmpty() { } public void testKeySetRetainAllNullFromEmpty() { - final Map map; + Map map; try { map = makeEmptyMap(); } catch (UnsupportedOperationException e) { @@ -1363,7 +1285,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 +1299,7 @@ public void testKeySetRetainAllNullFromEmpty() { } public void testValues() { - final Map map; - final Collection valueCollection; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1386,8 +1307,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 +1320,7 @@ public void testValues() { } public void testValuesIteratorRemove() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1417,24 +1338,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 +1363,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 +1396,7 @@ public void testValuesRemoveMissing() { } public void testValuesRemoveAll() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1503,17 +1414,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 +1433,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 +1447,7 @@ public void testValuesRemoveAllNullFromEmpty() { } public void testValuesRetainAll() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1557,17 +1465,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 +1484,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 +1498,7 @@ public void testValuesRetainAllNullFromEmpty() { } public void testValuesClear() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1605,16 +1510,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..f874387da2ae 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,7 +63,7 @@ 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( @@ -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; 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..4e55d7abde7c 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<>(); 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..f9a68a7d0693 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,7 +60,8 @@ 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); @@ -68,9 +71,8 @@ public TestSuite createTestSuite() { 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; + @SuppressWarnings("rawtypes") // class literals public 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..f38500e53388 --- /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(emulated = true) +@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..1abda002ef39 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() { @@ -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..ed927bbcb442 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,6 +80,8 @@ protected List createDerivedSuites( .named(getName() + " reserialized") .withFeatures(computeReserializedCollectionFeatures(parentBuilder.getFeatures())) .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); } return derivedSuites; 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..a77cc1fde216 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/SpliteratorTester.java @@ -0,0 +1,350 @@ +/* + * 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 +@SuppressWarnings("Java7ApiChecker") +@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 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..2ddd283db418 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() { @@ -54,7 +57,7 @@ public AnEnum[] createArray(int length) { /** Sorts the enums according to their natural ordering. */ @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..576e870c3ebe 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,6 +30,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public abstract class TestIntegerSortedSetGenerator extends TestIntegerSetGenerator { @Override protected abstract SortedSet create(Integer[] elements); @@ -35,7 +38,7 @@ public abstract class TestIntegerSortedSetGenerator extends TestIntegerSetGenera /** Sorts the elements by their natural ordering. */ @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..d83fc9d4ab73 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 { @@ -41,7 +44,7 @@ public SortedSet create(Object... elements) { /** Sorts the elements by their natural ordering. */ @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..4e8d009a35b3 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,23 +101,23 @@ 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(); } public Test testsForEmptyList() { @@ -121,7 +125,7 @@ public Test testsForEmptyList() { new TestStringListGenerator() { @Override public List create(String[] elements) { - return Collections.emptyList(); + return emptyList(); } }) .named("emptyList") @@ -135,7 +139,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 +156,7 @@ public Test testsForArraysAsList() { new TestStringListGenerator() { @Override public List create(String[] elements) { - return Arrays.asList(elements.clone()); + return asList(elements.clone()); } }) .named("Arrays.asList") @@ -232,7 +236,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") 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..c12984752a95 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,6 +17,10 @@ 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; @@ -75,55 +79,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 +191,7 @@ public Test testsForEmptyMap() { new TestStringMapGenerator() { @Override protected Map create(Entry[] entries) { - return Collections.emptyMap(); + return emptyMap(); } }) .named("emptyMap") @@ -201,7 +205,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") @@ -337,7 +341,7 @@ public Test testsForUnmodifiableMap() { new TestStringMapGenerator() { @Override protected Map create(Entry[] entries) { - return Collections.unmodifiableMap(toHashMap(entries)); + return unmodifiableMap(toHashMap(entries)); } }) .named("unmodifiableMap/HashMap") @@ -465,7 +469,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..734a57e9e984 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() { 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..8a889174aef1 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") @@ -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..eda5aac70239 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; 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/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..eb6802ec67a4 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 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..395faff7493b 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 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..4abcef48f15e 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,12 @@ 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.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 +35,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) { @@ -54,7 +56,7 @@ public final Set> getAbsentFeatures() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } 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..981f4b9e956e 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; @@ -47,7 +49,9 @@ * @author Chris Povirk */ @GwtCompatible(emulated = true) -@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 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..634a1a424c03 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.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(); @@ -57,7 +62,7 @@ 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)); } } } 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..08f3c0aa397e 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; @@ -39,7 +40,9 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) -@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 BiMapInverseTester extends AbstractBiMapTester { public void testInverseSame() { @@ -72,11 +75,13 @@ private static class BiMapPair implements Serializable { * 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..fe7229ff8159 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() {} @@ -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..7fdf762936e2 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.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.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.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,8 +74,7 @@ public void testAsMapRemoveImplementsList() { @CollectionSize.Require(SEVERAL) public void testEquals() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k1(), v0()), Helpers.mapEntry(k0(), v3())); + resetContainer(mapEntry(k0(), v0()), mapEntry(k1(), v0()), mapEntry(k0(), v3())); Map> expected = Maps.newHashMap(); expected.put(k0(), Lists.newArrayList(v0(), v3())); expected.put(k1(), Lists.newArrayList(v0())); @@ -77,22 +83,19 @@ 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 = newHashSet(); + 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) 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..27e7afd487ca 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,6 +103,9 @@ 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); } 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..28f55ca53034 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,12 @@ 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.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,15 +48,17 @@ * @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(); } } @@ -72,7 +76,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 +90,7 @@ public Collection create(UnhashableObject[] elements) { for (UnhashableObject value : elements) { builder.put(key++, value); } - return builder.build().values(); + return builder.buildOrThrow().values(); } } @@ -97,7 +101,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 +112,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 +132,7 @@ public SampleElements> samples() { @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override @@ -141,10 +145,10 @@ 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(); } } @@ -153,7 +157,7 @@ public static class ImmutableEnumMapGenerator extends TestEnumMapGenerator { protected Map create(Entry[] entries) { Map map = Maps.newHashMap(); for (Entry entry : entries) { - // checkArgument(!map.containsKey(entry.getKey())); + checkNotNull(entry); map.put(entry.getKey(), entry.getValue()); } return Maps.immutableEnumMap(map); @@ -188,16 +192,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 +206,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,7 +231,7 @@ 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]; } } 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..e78bebe0402a 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,16 +37,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 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()); } private void assertCleared() { 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..da478b3e42d0 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,8 +86,8 @@ 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())); } @@ -93,8 +95,8 @@ public void testRemoveAllPropagatesToMultimap() { @CollectionSize.Require(absent = ZERO) @MapFeature.Require(SUPPORTS_REMOVE) 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 +105,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..aa5f6542ca3b 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 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..d89227fa77b1 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,16 +35,16 @@ * @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) @@ -56,7 +57,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 +65,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..cc36b3c07669 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 @@ -21,6 +21,8 @@ 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; @@ -40,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 MultimapPutIterableTester extends AbstractMultimapTester> { @CollectionSize.Require(absent = ZERO) @MapFeature.Require(SUPPORTS_PUT) @@ -111,11 +115,8 @@ 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(), Lists.newArrayList(v3(), null))); Collection values = multimap().get(k3()); if (values.size() == 0) { @@ -133,11 +134,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(), Lists.newArrayList(null, v3()))); /* * In principle, a Multimap implementation could add e3 first before failing on the null. But @@ -156,14 +154,9 @@ public void testPutAllOnPresentNullKey() { 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()))); } @MapFeature.Require(SUPPORTS_PUT) 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..b25763d4aa21 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,15 +47,15 @@ * @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) @@ -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..20300f09bd62 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() { @@ -68,7 +72,7 @@ public void testRemoveNullKeyPresent() { assertTrue(multimap().remove(null, getValueForNullKey())); - expectMissing(Helpers.mapEntry((K) null, getValueForNullKey())); + expectMissing(mapEntry((K) null, getValueForNullKey())); assertGet(getKeyForNullValue(), ImmutableList.of()); } @@ -79,7 +83,7 @@ public void testRemoveNullValuePresent() { assertTrue(multimap().remove(getKeyForNullValue(), null)); - expectMissing(Helpers.mapEntry(getKeyForNullValue(), (V) null)); + expectMissing(mapEntry(getKeyForNullValue(), (V) null)); assertGet(getKeyForNullValue(), ImmutableList.of()); } @@ -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..afa9fb9f8823 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,8 +66,7 @@ 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()); @@ -75,8 +76,7 @@ public void testReplaceEmptyValues() { 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 +86,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 +95,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 +112,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 +120,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..ee755852117f 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,6 +74,7 @@ public static > MultimapTestSuiteBuilder } // Class parameters must be raw. + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { return ImmutableList.>of( @@ -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()); } @@ -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 @@ -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()) @@ -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,7 +626,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).asMap().get(k); } 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..f776c470c4d1 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 @@ -36,7 +36,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 MultimapValuesTester extends AbstractMultimapTester> { public void testValues() { List expected = Lists.newArrayList(); 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..b7ad8d8a3b25 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,30 +72,18 @@ 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()); } @@ -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..752cb09b798a 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; @@ -38,7 +40,9 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) -@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 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..c70383d0f855 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,7 +43,7 @@ public enum MultisetFeature implements Feature { @Override public Set> getImpliedFeatures() { - return Collections.emptySet(); + return emptySet(); } @Retention(RetentionPolicy.RUNTIME) 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..5c6c6c6f4233 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; /** @@ -37,15 +40,17 @@ * @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") +@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..67719e987628 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; @@ -42,25 +45,19 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) -@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 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..77f24ddf4712 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,6 +158,8 @@ TestSuite createElementSetTestSuite( .named(getName() + ".elementSet") .withFeatures(computeElementSetFeatures(parentBuilder.getFeatures())) .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite(); } @@ -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 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..92da5482f879 --- /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(emulated = true) +@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..137a3bc432ed 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 @@ -18,11 +18,15 @@ 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; @@ -44,12 +48,12 @@ 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.Collections; import java.util.Comparator; 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. @@ -59,6 +63,7 @@ * @author Hayward Chan */ @GwtCompatible(emulated = true) +@NullMarked public class SetGenerators { public static class ImmutableSetCopyOfGenerator extends TestStringSetGenerator { @@ -83,7 +88,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 +100,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 +112,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); } @@ -190,7 +195,7 @@ protected SortedSet create(String[] elements) { @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder, Collections.reverseOrder()); + sort(insertionOrder, Collections.reverseOrder()); return insertionOrder; } } @@ -207,7 +212,7 @@ protected SortedSet create(String[] elements) { @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder, Collections.reverseOrder()); + sort(insertionOrder, Collections.reverseOrder()); return insertionOrder; } } @@ -216,14 +221,12 @@ 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(); } @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder, Collections.reverseOrder()); + sort(insertionOrder, Collections.reverseOrder()); return insertionOrder; } } @@ -246,7 +249,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(); } } @@ -317,7 +320,7 @@ private static Ordering createExplicitComparator(String[] elements) { Set elementsPlus = Sets.newLinkedHashSet(); 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)); @@ -400,7 +403,7 @@ protected SortedSet create(Integer[] elements) { /** Sorts the elements in reverse natural order. */ @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder, Ordering.natural().reverse()); + sort(insertionOrder, Ordering.natural().reverse()); return insertionOrder; } } 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..a9045eae21a7 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,27 @@ 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.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 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 +65,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 +74,28 @@ public void testAsMapRemoveImplementsSet() { @CollectionSize.Require(SEVERAL) public void testEquals() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k1(), v0()), Helpers.mapEntry(k0(), v3())); + resetContainer(mapEntry(k0(), v0()), mapEntry(k1(), v0()), mapEntry(k0(), v3())); Map> expected = Maps.newHashMap(); - expected.put(k0(), Sets.newHashSet(v0(), v3())); - expected.put(k1(), Sets.newHashSet(v0())); + 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 = newHashSet(); + 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) 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..18ab4dfdd823 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); 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..d82fd9ab6bd0 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); } } 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..bed3dac9e5f5 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,10 +16,14 @@ 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; @@ -29,16 +33,13 @@ 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 +57,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 +72,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 +103,7 @@ enum NoRecurse implements Feature { @Override public Set> getImpliedFeatures() { - return Collections.emptySet(); + return emptySet(); } } @@ -138,8 +140,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 +154,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 +172,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 +188,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 = @@ -234,7 +235,7 @@ private List getExtremeValues() { } private TestSuite createDescendingSuite(SortedMultisetTestSuiteBuilder parentBuilder) { - final TestMultisetGenerator delegate = + TestMultisetGenerator delegate = (TestMultisetGenerator) parentBuilder.getSubjectGenerator(); Set> features = new HashSet<>(); @@ -263,7 +264,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()); 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..7c699cce29dd 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() { @@ -56,7 +59,7 @@ public AnEnum[] createArray(int length) { /** Sorts the enums according to their natural ordering. */ @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..ec64f375593e 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,27 @@ 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 +45,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 +115,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 +186,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 +207,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 +270,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(Lists.newArrayList(multimap.entries())); assertMultimapRemainsUnmodified(multimap, originalEntries); - Collection sampleValueAsCollection = Collections.singleton(sampleValue); + Collection sampleValueAsCollection = singleton(sampleValue); // Test #clear() try { @@ -283,7 +289,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 +301,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 +409,13 @@ 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()); } } 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..73e260fa3e59 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 { +@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..a54f16b8a6bb 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. +@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..50fff71570dc 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; @@ -36,7 +38,9 @@ * @author Chris Povirk */ @GwtCompatible(emulated = true) -@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 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..f381a3dd28d0 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,7 +26,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 CollectionEqualsTester extends AbstractCollectionTester { // TODO(cpovirk): Consider using EqualsTester from Guava. 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..f8c2d4c2cf11 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; /** @@ -48,23 +51,27 @@ * @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 { +@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..7d0955a6bd06 --- /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..e4cf4d7be81f 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..85bb60683ee7 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,9 +39,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 CollectionRetainAllTester extends AbstractCollectionTester { /** A collection of elements to retain, along with a description for use in failure messages. */ @@ -79,11 +81,11 @@ 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"); @@ -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..eea1b10406a9 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,7 +31,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 CollectionSerializationEqualTester extends AbstractCollectionTester { @CollectionFeature.Require(SERIALIZABLE) public void testReserialize() { 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..62d986b41edf --- /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(emulated = true) +@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..3bd15e6669ef 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; @@ -40,7 +44,9 @@ * @author Chris Povirk */ @GwtCompatible(emulated = true) -@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 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) @@ -181,21 +187,22 @@ public void testToArray_emptyArrayOfWrongTypeForEmptyCollection() { } private void expectArrayContentsAnyOrder(Object[] expected, Object[] actual) { - Helpers.assertEqualIgnoringOrder(Arrays.asList(expected), Arrays.asList(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..cab1041e5953 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/IgnoreJRERequirement.java @@ -0,0 +1,31 @@ +/* + * 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.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}) +@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..70f36426380d 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. +@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..3dc1154b65dc 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. +@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..fd5a3ee69a43 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; @@ -28,7 +30,9 @@ * @author George van den Driessche */ @GwtCompatible(emulated = true) -@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 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..a16b523e5be8 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; /** @@ -46,8 +51,11 @@ * @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 { +@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,19 +93,12 @@ 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() { @@ -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..aeec44920cf0 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; @@ -36,7 +38,9 @@ * @author George van den Driessche */ @GwtCompatible(emulated = true) -@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 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..b603358646e6 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. +@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,7 +70,7 @@ 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 */ } } @@ -96,7 +91,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 +100,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 +116,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 +129,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 +138,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 +156,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,7 +169,7 @@ 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); } @@ -189,7 +184,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 +204,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 +227,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 +308,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 +318,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 +334,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..aa9a779fa9eb 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 @@ -34,7 +34,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() { 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..46a43505531e 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; @@ -43,7 +45,9 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) -@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 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..8db00d3d47cf --- /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(emulated = true) +@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..16056a112523 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 { +@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..e03cc219c72f --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapPutIfAbsentTester.java @@ -0,0 +1,129 @@ +/* + * 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); + } +} 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..4185a06361dd 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. +@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..e9a0de2f04ed 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..d2e90ba6a007 --- /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(emulated = true) +@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..c9cf43da8b17 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; @@ -35,7 +36,9 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) -@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 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..8708789a1a2f 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; @@ -33,7 +34,9 @@ * @author George van den Driessche */ @GwtCompatible(emulated = true) -@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 SetHashCodeTester extends AbstractSetTester { public void testHashCode() { int expectedHashCode = 0; @@ -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..17a2908dbbd6 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..4fdceccda453 --- /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 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-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..0f84e106e4e7 100644 --- a/android/guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java +++ b/android/guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java @@ -20,8 +20,8 @@ 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; @@ -43,7 +43,6 @@ 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 +100,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 +115,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 +146,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 +232,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 +315,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 +326,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( @@ -415,8 +411,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 +423,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..214c1e168641 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,13 @@ package com.google.common.testing; import static com.google.common.base.Preconditions.checkArgument; +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; @@ -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,7 +239,7 @@ 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())) @@ -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); @@ -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,7 +406,8 @@ 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. @@ -430,6 +426,7 @@ public InMemoryPrintWriter() { } public static final class DeterministicRandom extends Random { + @Keep public DeterministicRandom() { super(0); } @@ -497,11 +494,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..fd9b24126bed 100644 --- a/android/guava-testlib/src/com/google/common/testing/ClassSanityTester.java +++ b/android/guava-testlib/src/com/google/common/testing/ClassSanityTester.java @@ -21,8 +21,8 @@ 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; @@ -33,7 +33,6 @@ 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; @@ -41,6 +40,7 @@ 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; @@ -52,7 +52,8 @@ 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 +79,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 +105,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 +136,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,6 +158,7 @@ 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); @@ -198,7 +203,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); @@ -290,8 +297,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; } @@ -334,13 +344,14 @@ 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; @@ -383,8 +394,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)); } @@ -429,6 +439,7 @@ private FactoryMethodReturnValueTester( * * @return this tester object */ + @CanIgnoreReturnValue public FactoryMethodReturnValueTester thatReturn(Class returnType) { this.returnTypeToTest = returnType; return this; @@ -442,6 +453,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 +462,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 +479,7 @@ public FactoryMethodReturnValueTester testNulls() throws Exception { * * @return this tester */ + @CanIgnoreReturnValue public FactoryMethodReturnValueTester testEquals() throws Exception { for (Invokable factory : getFactoriesToTest()) { try { @@ -489,17 +499,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 +524,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 +537,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); } } } @@ -564,11 +571,14 @@ public FactoryMethodReturnValueTester testEqualsAndSerializable() throws Excepti } private void testEqualsUsing(final Invokable factory) - throws ParameterNotInstantiableException, ParameterHasNoDistinctValueException, - IllegalAccessException, InvocationTargetException, FactoryMethodReturnsNullException { + 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); @@ -615,8 +625,10 @@ String reportItem(Item item) { */ private List generateEqualFactoryArguments( Invokable factory, List params, List args) - throws ParameterNotInstantiableException, FactoryMethodReturnsNullException, - InvocationTargetException, IllegalAccessException { + throws ParameterNotInstantiableException, + FactoryMethodReturnsNullException, + InvocationTargetException, + IllegalAccessException { List equalArgs = Lists.newArrayList(args); for (int i = 0; i < args.size(); i++) { Parameter param = params.get(i); @@ -626,7 +638,7 @@ private List generateEqualFactoryArguments( Object shouldBeEqualArg = generateDummyArg(param, newFreshValueGenerator()); if (arg != shouldBeEqualArg && Objects.equal(arg, shouldBeEqualArg) - && hashCodeInsensitiveToArgReference(factory, args, i, shouldBeEqualArg) + && hashCodeInsensitiveToArgReference(factory, args, i, checkNotNull(shouldBeEqualArg)) && hashCodeInsensitiveToArgReference( factory, args, i, generateDummyArg(param, newFreshValueGenerator()))) { // If the implementation uses identityHashCode(), referential equality is @@ -654,7 +666,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 +676,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; @@ -708,9 +719,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) @@ -734,7 +745,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 +772,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) { @@ -828,7 +838,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 index 7665ab1d435b..47232e8dd63b 100644 --- a/android/guava-testlib/src/com/google/common/testing/ClusterException.java +++ b/android/guava-testlib/src/com/google/common/testing/ClusterException.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import org.jspecify.annotations.NullMarked; /** * An {@link ClusterException} is a data structure that allows for some code to "throw multiple @@ -59,20 +60,21 @@ * @author Luiz-Otavio Zorzella */ @GwtCompatible +@NullMarked final class ClusterException extends RuntimeException { - public final Collection exceptions; + 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); + ArrayList temp = new ArrayList<>(exceptions); this.exceptions = Collections.unmodifiableCollection(temp); } - /** @see #create(Collection) */ - public static RuntimeException create(Throwable... exceptions) { + /** See {@link #create(Collection)}. */ + static RuntimeException create(Throwable... exceptions) { ArrayList temp = new ArrayList<>(Arrays.asList(exceptions)); return create(temp); } @@ -96,7 +98,7 @@ public static RuntimeException create(Throwable... exceptions) { * @throws NullPointerException if {@code exceptions} is null * @throws IllegalArgumentException if {@code exceptions} is empty */ - public static RuntimeException create(Collection exceptions) { + static RuntimeException create(Collection exceptions) { if (exceptions.size() == 0) { throw new IllegalArgumentException("Can't create an ExceptionCollection with no exceptions"); } 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..837ea36a4122 100644 --- a/android/guava-testlib/src/com/google/common/testing/DummyProxy.java +++ b/android/guava-testlib/src/com/google/common/testing/DummyProxy.java @@ -20,6 +20,7 @@ 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; @@ -30,6 +31,8 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; 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 +40,8 @@ * @author Ben Yu */ @GwtIncompatible +@J2ktIncompatible +@NullMarked abstract class DummyProxy { /** @@ -59,7 +64,7 @@ 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 TypeToken interfaceType; @@ -69,7 +74,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 +93,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()); 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..699acb38b568 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.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,8 +76,8 @@ * @author Jige Yu * @since 10.0 */ -@Beta @GwtCompatible +@NullMarked public final class EqualsTester { private static final int REPETITIONS = 3; @@ -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..bf16785bc5f1 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,14 @@ 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.errorprone.annotations.CanIgnoreReturnValue; import java.util.List; +import org.jspecify.annotations.NullMarked; /** * Tester for {@link Equivalence} relationships between groups of objects. @@ -49,8 +50,8 @@ * @author Gregory Kick * @since 10.0 */ -@Beta @GwtCompatible +@NullMarked public final class EquivalenceTester { private static final int REPETITIONS = 3; @@ -60,23 +61,24 @@ public final class EquivalenceTester { private EquivalenceTester(Equivalence equivalence) { this.equivalence = checkNotNull(equivalence); - this.delegate = - new RelationshipTester(equivalence, "equivalent", "hash", new ItemReporter()); + this.delegate = new RelationshipTester<>(equivalence, "equivalent", "hash", new ItemReporter()); } 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 +86,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..66ae972ca4d9 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,35 @@ 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 + @SuppressWarnings("Java7ApiChecker") // guava-android can rely on library desugaring now. + @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 +86,32 @@ 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 + @SuppressWarnings("Java7ApiChecker") // guava-android can rely on library desugaring now. + @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..193c9fe41859 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.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 @@ -53,8 +56,9 @@ * @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 = Lists.newArrayList(); 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..45be229ac4e0 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,8 +189,7 @@ 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))); } @@ -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); @@ -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) { + static ArrayList generateArrayList(@Nullable E freshElement) { ArrayList list = Lists.newArrayList(); list.add(freshElement); return list; } @Generates - private static LinkedList generateLinkedList(E freshElement) { + static LinkedList generateLinkedList(@Nullable E freshElement) { LinkedList list = Lists.newLinkedList(); 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) { + static LinkedHashSet generateLinkedHashSet(@Nullable E freshElement) { LinkedHashSet set = Sets.newLinkedHashSet(); 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) { + static LinkedHashMap generateLinkedHashMap(@Nullable K key, @Nullable V value) { LinkedHashMap map = Maps.newLinkedHashMap(); 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..c883dd7916ce 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. @@ -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(); + } + }; } /** @@ -263,7 +271,7 @@ public interface FinalizationPredicate { * * @throws RuntimeException if timed out or interrupted while waiting */ - public static void awaitClear(final WeakReference ref) { + public static void awaitClear(WeakReference ref) { awaitDone( new FinalizationPredicate() { @Override @@ -295,10 +303,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..2203162bf2b7 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/testing/IgnoreJRERequirement.java @@ -0,0 +1,29 @@ +/* + * 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.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}) +@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..fb2f9cc94d25 100644 --- a/android/guava-testlib/src/com/google/common/testing/NullPointerTester.java +++ b/android/guava-testlib/src/com/google/common/testing/NullPointerTester.java @@ -19,8 +19,8 @@ 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.annotations.J2ktIncompatible; import com.google.common.base.Converter; import com.google.common.base.Objects; import com.google.common.collect.ClassToInstanceMap; @@ -33,8 +33,8 @@ import com.google.common.reflect.Parameter; import com.google.common.reflect.Reflection; import com.google.common.reflect.TypeToken; +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; @@ -46,14 +46,14 @@ import java.util.List; 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,8 +66,9 @@ * @author Kevin Bourrillion * @since 10.0 */ -@Beta @GwtIncompatible +@J2ktIncompatible +@NullMarked public final class NullPointerTester { private final ClassToInstanceMap defaults = MutableClassToInstanceMap.create(); @@ -75,10 +76,27 @@ public final class NullPointerTester { private ExceptionTypePolicy policy = ExceptionTypePolicy.NPE_OR_UOE; + 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) { + // OK, fine: If it doesn't exist, then there's chance that we're going to be asked to test it. + } + } + /** * 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 +107,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 +118,7 @@ public NullPointerTester ignore(Method method) { * * @since 22.0 */ + @CanIgnoreReturnValue public NullPointerTester ignore(Constructor constructor) { ignoredMembers.add(checkNotNull(constructor)); return this; @@ -176,7 +196,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 +227,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 +325,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); @@ -329,11 +348,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 +377,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 +412,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") @@ -455,13 +480,13 @@ private static TypeToken getFirstTypeParameter(Type type) { private T newDefaultReturningProxy(final 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 +499,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(Invokable invokable) { + return NULLNESS_ANNOTATION_READER.isNullable(invokable); + } + + static boolean isNullable(Parameter param) { + return NULLNESS_ANNOTATION_READER.isNullable(param); + } - static boolean isNullable(AnnotatedElement e) { - for (Annotation annotation : e.getAnnotations()) { + private static boolean containsNullable(Annotation[] annotations) { + for (Annotation annotation : annotations) { if (NULLABLE_ANNOTATION_SIMPLE_NAMES.contains(annotation.annotationType().getSimpleName())) { return true; } @@ -549,4 +581,62 @@ public boolean isExpectedType(Throwable cause) { public 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 { + @SuppressWarnings("Java7ApiChecker") + 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..0352b5aa9ee5 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,6 +25,7 @@ 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. @@ -31,6 +33,7 @@ * @author Chris Povirk */ @GwtCompatible(emulated = true) +@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..fdfa23242daf 100644 --- a/android/guava-testlib/src/com/google/common/testing/RelationshipTester.java +++ b/android/guava-testlib/src/com/google/common/testing/RelationshipTester.java @@ -22,8 +22,10 @@ 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.List; import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; /** * Implementation helper for {@link EqualsTester} and {@link EquivalenceTester} that tests for @@ -32,6 +34,7 @@ * @author Gregory Kick */ @GwtCompatible +@NullMarked final class RelationshipTester { static class ItemReporter { @@ -66,6 +69,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 +151,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..f0c8dbd2ede8 100644 --- a/android/guava-testlib/src/com/google/common/testing/TearDownStack.java +++ b/android/guava-testlib/src/com/google/common/testing/TearDownStack.java @@ -18,8 +18,8 @@ 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.VisibleForTesting; import com.google.common.collect.Lists; import com.google.errorprone.annotations.concurrent.GuardedBy; import java.util.ArrayList; @@ -27,6 +27,7 @@ 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,12 +37,14 @@ * @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") + @VisibleForTesting final Object lock = new Object(); + + @GuardedBy("lock") final LinkedList stack = new LinkedList<>(); private final boolean suppressThrows; @@ -56,7 +59,7 @@ public TearDownStack(boolean suppressThrows) { @Override public final void addTearDown(TearDown tearDown) { - synchronized (stack) { + synchronized (lock) { stack.addFirst(checkNotNull(tearDown)); } } @@ -65,7 +68,7 @@ public final void addTearDown(TearDown tearDown) { public final void runTearDown() { List exceptions = new ArrayList<>(); List stackCopy; - synchronized (stack) { + synchronized (lock) { stackCopy = Lists.newArrayList(stack); stack.clear(); } 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..2baae7e9eb71 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,7 +16,10 @@ package com.google.common.util.concurrent.testing; -import com.google.common.annotations.Beta; +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; @@ -28,6 +31,7 @@ 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 +40,6 @@ * @author Sven Mawson * @since 10.0 */ -@Beta @GwtIncompatible public abstract class AbstractListenableFutureTest extends TestCase { @@ -60,7 +63,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 +71,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 = Executors.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 +93,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 +111,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 +129,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(); - 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 +154,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); } /** @@ -209,7 +174,7 @@ public void testAllListenersCompleteSuccessfully() ExecutorService exec = Executors.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 +182,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..4891e9a29474 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 @@ -31,6 +31,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -135,23 +136,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(Executors.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..068122588312 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(); } @@ -153,7 +155,7 @@ private static 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-testlib/test/com/google/common/collect/testing/FeatureSpecificTestSuiteBuilderTest.java b/android/guava-testlib/test/com/google/common/collect/testing/FeatureSpecificTestSuiteBuilderTest.java index dc0fe37b4471..72284f893bcd 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,22 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestResult; -import org.junit.Ignore; -/** @author Max Ross */ +/** + * @author Max Ross + */ 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 +46,7 @@ public void run() { } }; - final boolean tearDown[] = {false}; + boolean[] tearDown = {false}; Runnable tearDownRunnable = new Runnable() { @Override @@ -80,8 +65,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..b70a36d6abd6 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.emptyList; +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,27 @@ public void testNullsBeforeB() { public void testIsEmpty_iterable() { List list = new ArrayList<>(); - Helpers.assertEmpty(list); - Helpers.assertEmpty( + assertEmpty(list); + assertEmpty( new Iterable() { @Override public Iterator iterator() { - return Collections.emptyList().iterator(); + return emptyList().iterator(); } }); 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 +79,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..d7df4d3e117e 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 @@ -26,7 +26,6 @@ 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"); @@ -201,18 +200,18 @@ 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)); } @@ -233,7 +232,7 @@ public void remove() { @Override public Integer next() { // We should throw here, but we won't! - return null; + return 0; } @Override @@ -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..62724243fce2 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 @@ -26,6 +26,11 @@ 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,9 +41,11 @@ 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. @@ -52,6 +59,7 @@ public static Test suite() { TestSuite suite = new TestSuite(MapTestSuiteBuilderTests.class.getSimpleName()); suite.addTest(testsForHashMapNullKeysForbidden()); suite.addTest(testsForHashMapNullValuesForbidden()); + suite.addTest(testsForSetUpTearDown()); return suite; } @@ -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); } @@ -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); } @@ -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,89 @@ 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() { + final 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) { + LinkageError error = new LinkageError(cause.toString()); + error.initCause(cause); + return error; + } } 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/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..526cd566a627 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; @@ -41,12 +41,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..5ba946ee1cb2 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 @@ -26,9 +26,9 @@ 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.Collection; import java.util.List; import java.util.Map; @@ -40,10 +40,7 @@ * * @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. public class OpenJdk6MapTests extends TestsForMapsInJavaUtil { public static Test suite() { return new OpenJdk6MapTests().allTests(); @@ -51,7 +48,7 @@ public static Test suite() { @Override protected Collection suppressForTreeMapNatural() { - return Arrays.asList( + return asList( getPutNullKeyUnsupportedMethod(), getPutAllNullKeyUnsupportedMethod(), getCreateWithNullKeyUnsupportedMethod(), @@ -66,14 +63,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()); 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..be3e35a09e72 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; @@ -36,8 +36,7 @@ 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..39c5cf442b26 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; @@ -41,7 +41,7 @@ public static Test suite() { @Override protected Collection suppressForTreeSetNatural() { - return Arrays.asList( + return asList( getAddNullUnsupportedMethod(), getAddAllNullUnsupportedMethod(), getCreateWithNullUnsupportedMethod()); @@ -49,6 +49,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/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..67aa9f7f9b1f 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 @@ -110,36 +110,4 @@ public void testViewSerialization() { 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; - } - } } 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..91e2e9f261e0 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,6 +16,8 @@ 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; @@ -24,7 +26,6 @@ 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.Collections; import java.util.List; import java.util.Map; @@ -44,7 +45,7 @@ public static Test suite() { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return new SafeTreeSet<>(Arrays.asList(elements)); + return new SafeTreeSet<>(asList(elements)); } @Override 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..793efd942237 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 @@ -52,8 +52,7 @@ private static void assertGoodTesterAnnotation(Class annot 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(); assertTrue( 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..a77820938b37 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,297 @@ 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 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 { + @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 { + @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 { + @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 { + @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 { + @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 { + @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/ArbitraryInstancesTest.java b/android/guava-testlib/test/com/google/common/testing/ArbitraryInstancesTest.java index b47672f83dc6..bbd5c6404476 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,11 @@ package com.google.common.testing; 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.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; @@ -127,12 +129,15 @@ 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() { @@ -152,18 +157,18 @@ 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)); + assertEquals(SECONDS, ArbitraryInstances.get(TimeUnit.class)); assertNotNull(ArbitraryInstances.get(Object.class)); assertEquals(0, ArbitraryInstances.get(Number.class)); - assertEquals(Charsets.UTF_8, ArbitraryInstances.get(Charset.class)); + assertEquals(UTF_8, ArbitraryInstances.get(Charset.class)); assertNotNull(ArbitraryInstances.get(UUID.class)); } @@ -277,11 +282,7 @@ public void testGet_comparable() { Comparable comparable = ArbitraryInstances.get(Comparable.class); assertEquals(0, comparable.compareTo(comparable)); assertTrue(comparable.compareTo("") > 0); - try { - comparable.compareTo(null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> comparable.compareTo(null)); } public void testGet_array() { @@ -473,7 +474,7 @@ private WithGenericConstant() {} } public static class WithNullConstant { - public static final WithNullConstant NULL = null; + public static final @Nullable WithNullConstant NULL = null; private WithNullConstant() {} } @@ -496,7 +497,7 @@ private static class WithPublicConstants { private static class FirstConstantIsNull { // To test that null constant is ignored @SuppressWarnings("unused") - public static final FirstConstantIsNull FIRST = null; + public static final @Nullable FirstConstantIsNull FIRST = null; public static final FirstConstantIsNull SECOND = new FirstConstantIsNull(); } 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..7899a76ff231 100644 --- a/android/guava-testlib/test/com/google/common/testing/EqualsTesterTest.java +++ b/android/guava-testlib/test/com/google/common/testing/EqualsTesterTest.java @@ -16,6 +16,9 @@ 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; @@ -23,6 +26,8 @@ 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 +36,7 @@ */ @GwtCompatible @SuppressWarnings("MissingTestCall") +@NullUnmarked public class EqualsTesterTest extends TestCase { private ValidTestObject reference; private EqualsTester equalsTester; @@ -50,29 +56,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 +112,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 +192,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 +264,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) { @@ -302,7 +293,7 @@ private static class ValidTestObject { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (!(o instanceof ValidTestObject)) { return false; } @@ -337,7 +328,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 +347,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 +361,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 +375,7 @@ public int hashCode() { private static class InvalidEqualsIncompatibleClassObject { @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return o != null; } @@ -413,7 +404,7 @@ NamedObject addPeers(String... names) { } @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 +431,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..8529f8f14a66 100644 --- a/android/guava-testlib/test/com/google/common/testing/FakeTickerTest.java +++ b/android/guava-testlib/test/com/google/common/testing/FakeTickerTest.java @@ -16,9 +16,14 @@ package com.google.common.testing; +import static com.google.common.testing.ReflectionFreeAssertThrows.assertThrows; +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; @@ -26,6 +31,8 @@ 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}. @@ -33,6 +40,9 @@ * @author Jige Yu */ @GwtCompatible(emulated = true) +// 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,11 +116,8 @@ 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 @@ -108,9 +128,9 @@ public void testConcurrentAdvance() throws Exception { 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); + final 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,7 +164,7 @@ 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, final Callable<@Nullable Void> callable) throws Exception { ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads); final CountDownLatch startLatch = new CountDownLatch(numberOfThreads); @@ -154,9 +173,9 @@ private void runConcurrentTest(int numberOfThreads, final Callable callabl @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..f3eb11694bbf 100644 --- a/android/guava-testlib/test/com/google/common/testing/FreshValueGeneratorTest.java +++ b/android/guava-testlib/test/com/google/common/testing/FreshValueGeneratorTest.java @@ -106,12 +106,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? 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..7257bce99a25 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,52 +35,57 @@ * @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() { + public void testAwait_countDownLatch() { final CountDownLatch latch = new CountDownLatch(1); - Object x = + 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() { + final 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() { + final 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()); @@ -89,11 +97,12 @@ public void testAwaitClear() { assertNull(ref.get()); } - public void testAwaitDone_FinalizationPredicate() { + public void testAwaitDone_finalizationPredicate() { final WeakHashMap map = new WeakHashMap<>(); map.put(new Object(), Boolean.TRUE); GcFinalization.awaitDone( new FinalizationPredicate() { + @Override public boolean isDone() { return map.isEmpty(); } @@ -113,9 +122,11 @@ class Interruptenator extends Thread { this(interruptee, new AtomicBoolean(false)); } + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. Interruptenator(final Thread interruptee, final 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); - } + 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); - } + final 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); - } + 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(); @@ -218,6 +222,7 @@ public void testAwaitFullGc() { final WeakReference ref = new WeakReference( new Object() { + @SuppressWarnings({"removal", "Finalize"}) // b/260137033 @Override protected void finalize() { finalizerRan.countDown(); @@ -228,8 +233,8 @@ 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()); 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..d08735297ad5 --- /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(emulated = true) +@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..e5c597892fb8 100644 --- a/android/guava-testlib/test/com/google/common/testing/RelationshipTesterTest.java +++ b/android/guava-testlib/test/com/google/common/testing/RelationshipTesterTest.java @@ -18,12 +18,14 @@ 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() { 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..9a5a68dfb055 100644 --- a/android/guava-testlib/test/com/google/common/testing/TearDownStackTest.java +++ b/android/guava-testlib/test/com/google/common/testing/TearDownStackTest.java @@ -20,9 +20,14 @@ 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(); @@ -116,7 +121,7 @@ private TearDownStack buildTearDownStack() { @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,7 +151,7 @@ public void tearDown() throws Exception { private static final class SimpleTearDown implements TearDown { boolean ran = false; - Callback callback = null; + @Nullable Callback callback = null; public SimpleTearDown() {} 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..4af6b6b3ed94 100644 --- a/android/guava-testlib/test/com/google/common/testing/TestLogHandlerTest.java +++ b/android/guava-testlib/test/com/google/common/testing/TestLogHandlerTest.java @@ -20,12 +20,14 @@ 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; 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..e2d96b5394aa 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 @@ -119,7 +122,7 @@ public Runnable apply(final Runnable 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); @@ -141,7 +144,7 @@ public void testEqualsAndHashCodeForwarded() { public Runnable apply(final 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); @@ -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; } @@ -373,10 +376,10 @@ 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; } @@ -530,7 +533,7 @@ public String toString() { private interface Equals { @Override - boolean equals(Object obj); + boolean equals(@Nullable Object obj); @Override int hashCode(); @@ -579,7 +582,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 +596,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..dcc5532ea7bd 100644 --- a/android/guava-tests/benchmark/com/google/common/base/CharMatcherBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/base/CharMatcherBenchmark.java @@ -25,6 +25,7 @@ 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); @@ -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++) { 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..21e71774391e 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; } 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..3750ca53f9c2 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)) { @@ -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)) { @@ -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)) { 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..2793fc5e08d4 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; 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 index 75fc1f90d2ef..76e04fddfc81 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/BinaryTreeTraverserBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/BinaryTreeTraverserBenchmark.java @@ -21,12 +21,14 @@ import com.google.common.primitives.Ints; import java.util.List; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for the {@code TreeTraverser} operations on binary trees. * * @author Louis Wasserman */ +@NullUnmarked public class BinaryTreeTraverserBenchmark { private static class BinaryNode { final int x; 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..1001350a0513 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,7 @@ 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 com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; @@ -37,13 +38,15 @@ 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; @@ -192,7 +195,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 +238,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 +298,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 +333,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 +352,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) { + public boolean removeExactly(@Nullable Object element, int occurrences) { if (occurrences == 0) { return true; } @@ -518,7 +521,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 +546,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..a2c0f3976b95 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/ImmutableListCreationBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/ImmutableListCreationBenchmark.java @@ -19,12 +19,14 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; 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"}) 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..aa96b5ee67a5 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/InternersBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/InternersBenchmark.java @@ -17,18 +17,20 @@ package com.google.common.collect; import com.google.caliper.Benchmark; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarking interners. * * @author Dimitris Andreou */ +@NullUnmarked public class InternersBenchmark { @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; } @@ -37,7 +39,7 @@ int weakInterner(int reps) { 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; } 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..931d86cc9ed8 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; 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..4c71174b5073 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/MapBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/MapBenchmark.java @@ -17,6 +17,8 @@ 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; @@ -28,6 +30,7 @@ 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 +38,7 @@ * * @author Nicholaus Shupe */ +@NullUnmarked public class MapBenchmark { @Param({"Hash", "LinkedHM", "MapMaker1", "Immutable"}) private Impl impl; @@ -63,7 +67,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 +143,7 @@ Map create(Collection keys) { for (Element element : keys) { builder.put(element, element); } - return builder.build(); + return builder.buildOrThrow(); } }, ImmutableSorted { @@ -186,7 +190,7 @@ void setUp() { if (sortedData) { List valueList = newArrayList(sampleData.getValuesInSet()); - Collections.sort(valueList); + 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..afb01a2ef654 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,16 @@ 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.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({ 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 index 0fa3e2a88ebd..5fe37f971697 100644 --- a/android/guava-tests/benchmark/com/google/common/hash/ChecksumBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/hash/ChecksumBenchmark.java @@ -23,6 +23,7 @@ import java.util.zip.Adler32; import java.util.zip.CRC32; import java.util.zip.Checksum; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for comparing {@link Checksum}s and {@link HashFunction}s that wrap {@link Checksum}s. @@ -35,6 +36,7 @@ * * @author Colin Decker */ +@NullUnmarked public class ChecksumBenchmark { // Use a constant seed for all of the benchmarks to ensure apples to apples comparisons. @@ -63,12 +65,19 @@ byte crc32Checksum(int reps) throws Exception { byte result = 0x01; for (int i = 0; i < reps; i++) { CRC32 checksum = new CRC32(); - checksum.update(testBytes); + checksum.update(testBytes, 0, testBytes.length); result = (byte) (result ^ checksum.getValue()); } return result; } + // CRC32C + + @Benchmark + byte crc32cHashFunction(int reps) { + return runHashFunction(reps, Hashing.crc32c()); + } + // Adler32 @Benchmark @@ -81,12 +90,29 @@ byte adler32Checksum(int reps) throws Exception { byte result = 0x01; for (int i = 0; i < reps; i++) { Adler32 checksum = new Adler32(); - checksum.update(testBytes); + checksum.update(testBytes, 0, testBytes.length); result = (byte) (result ^ checksum.getValue()); } return result; } + // Fingerprint2011 + + @Benchmark + byte fingerprintHashFunction(int reps) { + return runHashFunction(reps, Hashing.fingerprint2011()); + } + + @Benchmark + byte fingerprintChecksum(int reps) throws Exception { + byte result = 0x01; + for (int i = 0; i < reps; i++) { + HashCode checksum = new Fingerprint2011().hashBytes(testBytes, 0, testBytes.length); + result = (byte) (result ^ checksum.asLong()); + } + return result; + } + // Helpers + main private byte runHashFunction(int reps, HashFunction hashFunction) { @@ -94,6 +120,7 @@ private byte runHashFunction(int reps, HashFunction hashFunction) { // 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]; + result ^= Hashing.fingerprint2011().hashInt(reps).asBytes()[0]; for (int i = 0; i < reps; i++) { result ^= hashFunction.hashBytes(testBytes).asBytes()[0]; } 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..d7b6eb110454 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 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..6dc7c0963569 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; @@ -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..8eca5a99cced 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; 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..cdc9ec4ce6b6 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); 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..b6ff61f25d9f 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; } 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..4105c1c49b46 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 { 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..730284d79cf9 100644 --- a/android/guava-tests/benchmark/com/google/common/math/IntMathBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/IntMathBenchmark.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 IntMath}. * * @author Louis Wasserman */ +@NullUnmarked public class IntMathBenchmark { private static int[] exponent = new int[ARRAY_SIZE]; private static int[] factorial = 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..d55d7b1069d5 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 = @@ -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..c37b1610e50b 100644 --- a/android/guava-tests/benchmark/com/google/common/math/StatsBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/StatsBenchmark.java @@ -22,12 +22,14 @@ 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 { 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..b4dd6202da5f 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 { @@ -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..9eb6cdd465e0 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; @@ -34,14 +35,15 @@ 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,7 +226,7 @@ void setUp() throws Exception { NUM_THREADS, NUM_THREADS, Long.MAX_VALUE, - TimeUnit.SECONDS, + SECONDS, new ArrayBlockingQueue(1000)); executorService.prestartAllCoreThreads(); final AtomicInteger integer = new AtomicInteger(); @@ -440,7 +420,7 @@ 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; @@ -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 RunnableExecutorPair next; RunnableExecutorPair(Runnable runnable, Executor executor, RunnableExecutorPair next) { this.runnable = runnable; @@ -504,10 +484,10 @@ 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; @@ -563,7 +543,7 @@ private static void executeListener(Runnable runnable, Executor executor) { private static final class RunnableExecutorPair { Runnable runnable; Executor executor; - @NullableDecl RunnableExecutorPair next; + @Nullable RunnableExecutorPair next; RunnableExecutorPair(Runnable runnable, Executor executor) { this.runnable = runnable; @@ -571,124 +551,4 @@ private static final class RunnableExecutorPair { } } } - - // 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; - - 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..d1ce1a154a5e 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 @@ -21,7 +21,6 @@ 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 +33,7 @@ import java.io.IOException; import java.net.URISyntaxException; import java.security.GeneralSecurityException; -import java.security.acl.NotOwnerException; +import java.security.KeyException; 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 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..cc04a97201da 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; @@ -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,12 +226,13 @@ 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(); @@ -249,6 +256,7 @@ public boolean offer(E e) { * @throws InterruptedException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ + @CanIgnoreReturnValue @Override public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { @@ -285,8 +293,9 @@ public void put(E e) throws InterruptedException { } } + @CanIgnoreReturnValue @Override - public E poll() { + public @Nullable E poll() { final Monitor monitor = this.monitor; if (monitor.enterIf(notEmpty)) { try { @@ -299,8 +308,9 @@ public E poll() { } } + @CanIgnoreReturnValue @Override - public E poll(long timeout, TimeUnit unit) throws InterruptedException { + public @Nullable E poll(long timeout, TimeUnit unit) throws InterruptedException { final Monitor monitor = this.monitor; if (monitor.enterWhen(notEmpty, timeout, unit)) { try { @@ -313,6 +323,7 @@ public E poll(long timeout, TimeUnit unit) throws InterruptedException { } } + @CanIgnoreReturnValue @Override public E take() throws InterruptedException { final Monitor monitor = this.monitor; @@ -324,8 +335,9 @@ public E take() throws InterruptedException { } } + @CanIgnoreReturnValue @Override - public E peek() { + public @Nullable E peek() { final Monitor monitor = this.monitor; if (monitor.enterIf(notEmpty)) { try { @@ -345,6 +357,7 @@ public E peek() { * * @return the number of elements in this queue */ + @CanIgnoreReturnValue @Override public int size() { final Monitor monitor = this.monitor; @@ -361,12 +374,13 @@ 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; @@ -380,15 +394,16 @@ 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; @@ -410,15 +425,16 @@ 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; @@ -447,6 +463,7 @@ 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; @@ -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,6 +512,7 @@ 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; @@ -522,6 +540,7 @@ public T[] toArray(T[] a) { } } + @CanIgnoreReturnValue @Override public String toString() { final Monitor monitor = this.monitor; @@ -563,6 +582,7 @@ public void clear() { * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */ + @CanIgnoreReturnValue @Override public int drainTo(Collection c) { if (c == null) throw new NullPointerException(); @@ -597,6 +617,7 @@ 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(); @@ -626,14 +647,15 @@ 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; @@ -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 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..85923d826caf 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,28 +30,29 @@ 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. * *

      * class FIFOEntry<E extends Comparable<? super E>>
    @@ -76,7 +77,7 @@
      * @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 +97,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 +148,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,11 +163,12 @@ 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;
    @@ -188,11 +191,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,8 +217,9 @@ public void put(E e) {
         offer(e); // never need to block
       }
     
    +  @CanIgnoreReturnValue // pushed down from class to method
       @Override
    -  public E poll() {
    +  public @Nullable E poll() {
         final Monitor monitor = this.monitor;
         monitor.enter();
         try {
    @@ -224,8 +229,9 @@ public E poll() {
         }
       }
     
    +  @CanIgnoreReturnValue // pushed down from class to method
       @Override
    -  public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    +  public @Nullable E poll(long timeout, TimeUnit unit) throws InterruptedException {
         final Monitor monitor = this.monitor;
         if (monitor.enterWhen(notEmpty, timeout, unit)) {
           try {
    @@ -238,6 +244,7 @@ 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;
    @@ -249,8 +256,9 @@ public E take() throws InterruptedException {
         }
       }
     
    +  @CanIgnoreReturnValue // pushed down from class to method
       @Override
    -  public E peek() {
    +  public @Nullable E peek() {
         final Monitor monitor = this.monitor;
         monitor.enter();
         try {
    @@ -261,16 +269,18 @@ 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;
    @@ -283,11 +293,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,10 +311,11 @@ 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) {
    +  public boolean remove(@Nullable Object o) {
         final Monitor monitor = this.monitor;
         monitor.enter();
         try {
    @@ -319,10 +331,11 @@ 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) {
    +  public boolean contains(@Nullable Object o) {
         final Monitor monitor = this.monitor;
         monitor.enter();
         try {
    @@ -344,6 +357,7 @@ 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;
    @@ -363,19 +377,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,6 +398,7 @@ 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; @@ -395,6 +410,7 @@ public T[] toArray(T[] a) { } } + @CanIgnoreReturnValue // pushed down from class to method @Override public String toString() { final Monitor monitor = this.monitor; @@ -412,6 +428,7 @@ 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(); @@ -437,6 +454,7 @@ 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(); @@ -474,13 +492,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 +516,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 +554,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..2513d861776b 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"}) 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..b42305e2b3e1 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 { 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..0d8cfd59a9b9 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; @@ -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..a0170657f827 100644 --- a/android/guava-tests/pom.xml +++ b/android/guava-tests/pom.xml @@ -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.0 + 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 @@ -102,6 +104,9 @@ 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..cb7d4306dc8a 100644 --- a/android/guava-tests/test/com/google/common/base/AbstractIteratorTest.java +++ b/android/guava-tests/test/com/google/common/base/AbstractIteratorTest.java @@ -16,13 +16,21 @@ 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}. @@ -30,6 +38,7 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@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,31 +89,18 @@ 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() { @@ -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..adba32ca9587 100644 --- a/android/guava-tests/test/com/google/common/base/BenchmarkHelpers.java +++ b/android/guava-tests/test/com/google/common/base/BenchmarkHelpers.java @@ -14,12 +14,15 @@ package com.google.common.base; +import org.jspecify.annotations.NullUnmarked; + /** * Common benchmarking utilities. * * @author Christopher Swenson * @author Louis Wasserman */ +@NullUnmarked class BenchmarkHelpers { private static final String WHITESPACE_CHARACTERS = "\u00a0\u180e\u202f\t\n\013\f\r \u0085" 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..ac9efb1f7fea 100644 --- a/android/guava-tests/test/com/google/common/base/CaseFormatTest.java +++ b/android/guava-tests/test/com/google/common/base/CaseFormatTest.java @@ -24,9 +24,11 @@ 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}. @@ -34,6 +36,7 @@ * @author Mike Bostock */ @GwtCompatible(emulated = true) +@NullUnmarked public class CaseFormatTest extends TestCase { public void testIdentity() { @@ -46,6 +49,7 @@ public void testIdentity() { } } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullArguments() { NullPointerTester tester = new NullPointerTester(); 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..248e4bbca070 100644 --- a/android/guava-tests/test/com/google/common/base/CharMatcherTest.java +++ b/android/guava-tests/test/com/google/common/base/CharMatcherTest.java @@ -27,6 +27,7 @@ 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,6 +37,7 @@ import java.util.Set; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Unit test for {@link CharMatcher}. @@ -43,8 +45,10 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@NullMarked public class CharMatcherTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testStaticNullPointers() throws Exception { NullPointerTester tester = new NullPointerTester(); @@ -90,6 +94,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 +156,7 @@ public void testEmpty() throws Exception { doTestEmpty(forPredicate(Predicates.equalTo('c'))); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNull() throws Exception { doTestNull(CharMatcher.any()); @@ -196,6 +202,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 +293,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 +311,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'); @@ -386,7 +396,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 +654,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 +737,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)); } } 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..94dc1a115e2a 100644 --- a/android/guava-tests/test/com/google/common/base/CharsetsTest.java +++ b/android/guava-tests/test/com/google/common/base/CharsetsTest.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.nio.charset.Charset; import java.util.Arrays; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link Charsets}. @@ -28,13 +30,16 @@ * @author Mike Bostock */ @GwtCompatible(emulated = true) +@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 +49,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); 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..e8817ab32e27 100644 --- a/android/guava-tests/test/com/google/common/base/ConverterTest.java +++ b/android/guava-tests/test/com/google/common/base/ConverterTest.java @@ -19,6 +19,8 @@ import static com.google.common.base.Functions.toStringFunction; 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 +29,11 @@ import java.util.Iterator; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Unit tests for {@link Converter}. */ -@GwtCompatible +@GwtCompatible(emulated = true) +@NullUnmarked public class ConverterTest extends TestCase { private static final Converter STR_TO_LONG = @@ -99,6 +103,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)); } @@ -111,6 +117,7 @@ public StringWrapper(String value) { } } + @GwtIncompatible // J2CL generics problem public void testAndThen() { Converter first = new Converter() { @@ -137,9 +144,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(); @@ -173,6 +183,8 @@ public Integer apply(String input) { 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")); @@ -213,6 +225,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..3a95ab08f6a1 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,7 +38,7 @@ 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()); + assertThat(Defaults.defaultValue(double.class).doubleValue()).isEqualTo(0.0d); assertNull(Defaults.defaultValue(void.class)); assertNull(Defaults.defaultValue(String.class)); } 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..394bbd3689b2 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,9 @@ public void testGetIfPresent_whenNoMatchingConstant() { } + @J2ktIncompatible @GwtIncompatible // weak references + @AndroidIncompatible // depends on details of GC and classloading public void testGetIfPresent_doesNotPreventClassUnloading() throws Exception { WeakReference shadowLoaderReference = doTestClassUnloading(); GcFinalization.awaitClear(shadowLoaderReference); @@ -91,6 +95,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. + @J2ktIncompatible @GwtIncompatible // weak references private WeakReference doTestClassUnloading() throws Exception { URLClassLoader shadowLoader = new URLClassLoader(getClassPathUrls(), null); @@ -112,6 +117,7 @@ private WeakReference doTestClassUnloading() throws Exception { return new WeakReference<>(shadowLoader); } + @GwtIncompatible // stringConverter public void testStringConverter_convert() { Converter converter = Enums.stringConverter(TestEnum.class); assertEquals(TestEnum.CHEETO, converter.convert("CHEETO")); @@ -121,15 +127,13 @@ public void testStringConverter_convert() { assertNull(converter.reverse().convert(null)); } + @GwtIncompatible // stringConverter public void testStringConverter_convertError() { Converter converter = Enums.stringConverter(TestEnum.class); - try { - converter.convert("xxx"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> converter.convert("xxx")); } + @GwtIncompatible // stringConverter public void testStringConverter_reverse() { Converter converter = Enums.stringConverter(TestEnum.class); assertEquals("CHEETO", converter.reverse().convert(TestEnum.CHEETO)); @@ -137,19 +141,22 @@ public void testStringConverter_reverse() { assertEquals("POODLE", converter.reverse().convert(TestEnum.POODLE)); } - @GwtIncompatible // NullPointerTester + @J2ktIncompatible + @GwtIncompatible // stringConverter public void testStringConverter_nullPointerTester() throws Exception { Converter converter = Enums.stringConverter(TestEnum.class); NullPointerTester tester = new NullPointerTester(); tester.testAllPublicInstanceMethods(converter); } + @GwtIncompatible // stringConverter public void testStringConverter_nullConversions() { Converter converter = Enums.stringConverter(TestEnum.class); assertNull(converter.convert(null)); assertNull(converter.reverse().convert(null)); } + @J2ktIncompatible @GwtIncompatible // Class.getName() public void testStringConverter_toString() { assertEquals( @@ -157,10 +164,12 @@ public void testStringConverter_toString() { Enums.stringConverter(TestEnum.class).toString()); } + @GwtIncompatible // stringConverter public void testStringConverter_serialization() { SerializableTester.reserializeAndAssert(Enums.stringConverter(TestEnum.class)); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); @@ -176,6 +185,7 @@ private enum AnEnum { BAR } + @J2ktIncompatible @GwtIncompatible // reflection public void testGetField() { Field foo = Enums.getField(AnEnum.FOO); @@ -187,6 +197,7 @@ public void testGetField() { assertFalse(bar.isAnnotationPresent(ExampleAnnotation.class)); } + @J2ktIncompatible @GwtIncompatible // Class.getClassLoader() private URL[] getClassPathUrls() { ClassLoader classLoader = getClass().getClassLoader(); @@ -200,6 +211,7 @@ private URL[] getClassPathUrls() { * System#getProperty system property}. */ // TODO(b/65488446): Make this a public API. + @J2ktIncompatible @GwtIncompatible private static ImmutableList parseJavaClassPath() { ImmutableList.Builder urls = ImmutableList.builder(); @@ -211,9 +223,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..5de187b1b8be 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 */ +@NullMarked @GwtCompatible(emulated = true) 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..c4236becab70 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,13 +73,6 @@ 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(); URLClassLoader sepLoader = new URLClassLoader(getClassPathUrls(), myLoader.getParent()); @@ -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..66a07ffaf8fe 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()); - 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..9533c00af2e7 100644 --- a/android/guava-tests/test/com/google/common/base/FunctionsTest.java +++ b/android/guava-tests/test/com/google/common/base/FunctionsTest.java @@ -16,8 +16,11 @@ 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 com.google.common.collect.Maps; import com.google.common.testing.ClassSanityTester; @@ -27,6 +30,8 @@ import java.io.Serializable; import java.util.Map; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Functions}. @@ -35,10 +40,11 @@ * @author Vlad Patryshev */ @GwtCompatible(emulated = true) +@NullMarked public class FunctionsTest extends TestCase { public void testIdentity_same() { - Function identity = Functions.identity(); + Function<@Nullable String, @Nullable String> identity = Functions.identity(); assertNull(identity.apply(null)); assertSame("foo", identity.apply("foo")); } @@ -48,6 +54,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 +73,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 +90,17 @@ public void testNullPointerExceptions() { } public void testForMapWithoutDefault() { - Map map = Maps.newHashMap(); + Map map = Maps.newHashMap(); 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")); - try { - function.apply("Two"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> function.apply("Two")); new EqualsTester() .addEqualityGroup(function, Functions.forMap(map)) @@ -107,17 +108,18 @@ 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 = Maps.newHashMap(); 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()); @@ -132,6 +134,7 @@ public void testForMapWithDefault() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testForMapWithDefault_includeSerializable() { Map map = Maps.newHashMap(); @@ -152,6 +155,7 @@ public void testForMapWithDefault_includeSerializable() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testForMapWithDefaultSerializable() { checkCanReserialize(Functions.forMap(ImmutableMap.of(1, 2), 3)); @@ -159,7 +163,7 @@ 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")); @@ -171,6 +175,7 @@ public void testForMapWithDefault_null() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testForMapWithDefault_null_compareWithSerializable() { ImmutableMap map = ImmutableMap.of("One", 1); @@ -215,17 +220,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,6 +232,7 @@ public void testComposition() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testComposition_includeReserializabled() { Map mJapaneseToInteger = Maps.newHashMap(); @@ -269,13 +267,13 @@ public void testCompositionWildcard() { 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 +330,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 +353,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)); @@ -378,7 +378,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 +393,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 +406,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 +426,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 +447,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..cdb4206dea25 100644 --- a/android/guava-tests/test/com/google/common/base/JoinerTest.java +++ b/android/guava-tests/test/com/google/common/base/JoinerTest.java @@ -16,23 +16,28 @@ 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.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}. @@ -40,92 +45,129 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@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 = Maps.newLinkedHashMap(); 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 = Maps.newLinkedHashMap(); 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..3b7c8068953b --- /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(emulated = true) +@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..4d277975c3ae 100644 --- a/android/guava-tests/test/com/google/common/base/ObjectsTest.java +++ b/android/guava-tests/test/com/google/common/base/ObjectsTest.java @@ -18,8 +18,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 junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link Objects}. @@ -27,6 +29,7 @@ * @author Laurence Gonsalves */ @GwtCompatible(emulated = true) +@NullUnmarked public class ObjectsTest extends TestCase { public void testEqual() throws Exception { @@ -46,7 +49,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 +61,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..8961f1f228f7 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 */ +@NullMarked @GwtCompatible(emulated = true) public final class OptionalTest extends TestCase { + @SuppressWarnings("NullOptional") + public void testToJavaUtil_static() { + assertNull(Optional.toJavaUtil(null)); + 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() { + assertNull(Optional.fromJavaUtil(null)); + 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,28 +121,27 @@ 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()); } @@ -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..7e8d8bb55dbd 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 */ +@NullMarked +@SuppressWarnings("LenientFormatStringValidation") // Intentional for testing @GwtCompatible(emulated = true) 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..cafa435a1133 100644 --- a/android/guava-tests/test/com/google/common/base/PredicatesTest.java +++ b/android/guava-tests/test/com/google/common/base/PredicatesTest.java @@ -18,9 +18,11 @@ import static com.google.common.base.CharMatcher.whitespace; import static com.google.common.collect.Lists.newArrayList; +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; import com.google.common.testing.ClassSanityTester; import com.google.common.testing.EqualsTester; @@ -36,30 +38,33 @@ 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 */ +@NullMarked @GwtCompatible(emulated = true) 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 { + static class IsOdd implements Predicate<@Nullable Integer>, Serializable { 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(); @@ -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,27 @@ 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)); + Predicate<@Nullable Integer> vacuouslyFalse = + Predicates.or(Collections.>emptyList()); + Predicate<@Nullable Integer> 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> trueAndFalse = Predicates.or(Arrays.asList(TRUE, FALSE)); assertEvalsToFalse(vacuouslyFalse); assertEvalsToTrue(troo); assertEvalsToTrue(trueAndFalse); } - @SuppressWarnings("unchecked") // varargs public void testOr_equalityIterable() { new EqualsTester() .addEqualityGroup( @@ -457,17 +450,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(); @@ -502,7 +495,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 +506,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 +545,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 +555,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 +565,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 +583,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 +597,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 +609,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 +618,7 @@ public void testSubtypeOf_interface() { assertTrue(isComparable.apply(Float.class)); } + @J2ktIncompatible @GwtIncompatible // Predicates.subtypeOf public void testSubtypeOf_equality() { new EqualsTester() @@ -630,6 +628,7 @@ public void testSubtypeOf_equality() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // Predicates.subtypeOf, SerializableTester public void testSubtypeOf_serialization() { Predicate> predicate = Predicates.subtypeOf(Integer.class); @@ -645,7 +644,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 +656,7 @@ public void testIsNull_equality() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testIsNull_serialization() { Predicate pre = Predicates.isNull(); @@ -666,7 +666,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 +678,7 @@ public void testNotNull_equality() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testNotNull_serialization() { checkSerialization(Predicates.notNull()); @@ -685,7 +686,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 +710,39 @@ 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 { + class CollectionThatThrowsNullPointerException extends ArrayList { + @J2ktIncompatible // Kotlin doesn't support companions for inner classes 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 { + class CollectionThatThrowsClassCastException extends ArrayList { + @J2ktIncompatible // Kotlin doesn't support companions for inner classes 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 +761,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 +820,7 @@ public void testCompose() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testComposeSerialization() { Function trim = TrimStringFunction.INSTANCE; @@ -843,6 +849,7 @@ public void testContains_apply() { assertFalse(isFoobar.apply("Foobarx")); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testContainsPattern_nulls() throws Exception { NullPointerTester tester = new NullPointerTester(); @@ -851,6 +858,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 +867,7 @@ public void testContains_nulls() throws Exception { tester.testAllPublicInstanceMethods(isWooPattern); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testContainsPattern_serialization() { Predicate pre = Predicates.containsPattern("foo"); @@ -877,13 +886,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 +906,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; @@ -956,9 +967,11 @@ private static void assertEvalsLike( } } + @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..f583b4bf043a --- /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(emulated = true) +@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/SneakyThrows.java b/android/guava-tests/test/com/google/common/base/SneakyThrows.java new file mode 100644 index 000000000000..33e2714ce43c --- /dev/null +++ b/android/guava-tests/test/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-tests/test/com/google/common/base/SplitterTest.java b/android/guava-tests/test/com/google/common/base/SplitterTest.java index 46928aa36a3b..d5b5a9aeb373 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 */ +/** + * @author Julien Silland + */ +@NullMarked @GwtCompatible(emulated = true) 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()); @@ -249,11 +258,7 @@ public void testStringSplitWithDelimiterSubstringInValue() { } public void testStringSplitWithEmptyString() { - try { - Splitter.on(""); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Splitter.on("")); } public void testStringSplitOnEmptyString() { @@ -352,6 +357,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 +371,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 +388,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,11 +447,7 @@ 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 @@ -489,6 +493,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 +561,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 +653,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 +712,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 +737,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 +751,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 +773,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/StopwatchJavaTimeTest.java b/android/guava-tests/test/com/google/common/base/StopwatchJavaTimeTest.java new file mode 100644 index 000000000000..71bcfc740779 --- /dev/null +++ b/android/guava-tests/test/com/google/common/base/StopwatchJavaTimeTest.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.base; + +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 the {@code java.time} support in {@link Stopwatch}. */ +@J2ktIncompatible +@GwtIncompatible +@NullUnmarked +public class StopwatchJavaTimeTest extends TestCase { + private final FakeTicker ticker = new FakeTicker(); + private final Stopwatch stopwatch = new Stopwatch(ticker); + + 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/StopwatchTest.java b/android/guava-tests/test/com/google/common/base/StopwatchTest.java index b85ebb7b0e94..53d583b1b17b 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,16 @@ 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.J2ktIncompatible; import com.google.common.testing.FakeTicker; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link Stopwatch}. @@ -30,6 +33,7 @@ * @author Kevin Bourrillion */ @GwtCompatible +@NullUnmarked public class StopwatchTest extends TestCase { private final FakeTicker ticker = new FakeTicker(); @@ -58,11 +62,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 +73,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 +158,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()); 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..79fd2651cd24 100644 --- a/android/guava-tests/test/com/google/common/base/StringsTest.java +++ b/android/guava-tests/test/com/google/common/base/StringsTest.java @@ -16,18 +16,22 @@ 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 */ +@NullMarked @GwtCompatible(emulated = true) public class StringsTest extends TestCase { public void testNullToEmpty() { @@ -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)); @@ -229,6 +216,10 @@ public void testLenientFormat() { 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 +227,8 @@ public void testLenientFormat() { public void testLenientFormat_badArgumentToString() { assertThat(Strings.lenientFormat("boiler %s plate", new ThrowsOnToString())) .matches( - "boiler plate"); } @@ -252,6 +244,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..10cd0b040e2a 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,6 +47,7 @@ * @author Laurence Gonsalves * @author Harry Heymann */ +@NullMarked @GwtCompatible(emulated = true) public class SuppliersTest extends TestCase { @@ -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; @@ -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); + Supplier<@Nullable Integer> nullSupplier = Suppliers.ofInstance(null); assertNull(nullSupplier.get()); } + @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,14 +351,15 @@ 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); + final long timeout = SECONDS.toNanos(60); final Supplier supplier = new Supplier() { @@ -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. @@ -380,8 +429,9 @@ 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 = new Supplier() { @@ -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..4ae5ad110da3 100644 --- a/android/guava-tests/test/com/google/common/base/ThrowablesTest.java +++ b/android/guava-tests/test/com/google/common/base/ThrowablesTest.java @@ -16,23 +16,37 @@ 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}. @@ -40,591 +54,241 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@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..7129a7011d56 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 = @@ -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 = @@ -223,11 +230,7 @@ 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 @@ -243,7 +246,7 @@ public void testToStringLenient_addWithNullValue() { } @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) @@ -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..72da351b5e24 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}. @@ -40,6 +42,7 @@ * @author Clément Roux */ @GwtCompatible(emulated = true) +@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..6a1e8a0a0785 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 +@GwtCompatible(emulated = true) +@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..ed42350b30a7 100644 --- a/android/guava-tests/test/com/google/common/cache/AbstractCacheTest.java +++ b/android/guava-tests/test/com/google/common/cache/AbstractCacheTest.java @@ -16,6 +16,8 @@ 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; @@ -24,12 +26,15 @@ 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() { @@ -37,7 +42,7 @@ public void testGetIfPresent() { Cache cache = new AbstractCache() { @Override - public Object getIfPresent(Object key) { + public @Nullable Object getIfPresent(Object key) { return valueRef.get(); } }; @@ -53,7 +58,7 @@ public void testGetAllPresent_empty() { Cache cache = new AbstractCache() { @Override - public Object getIfPresent(Object key) { + public @Nullable Object getIfPresent(Object key) { return null; } }; @@ -67,7 +72,7 @@ public void testGetAllPresent_cached() { Cache cache = new AbstractCache() { @Override - public Object getIfPresent(Object key) { + public @Nullable Object getIfPresent(Object key) { return cachedKey.equals(key) ? cachedValue : null; } }; @@ -102,14 +107,14 @@ public void testEmptySimpleStats() { CacheStats stats = counter.snapshot(); assertEquals(0, stats.requestCount()); assertEquals(0, stats.hitCount()); - assertEquals(1.0, stats.hitRate()); + assertThat(stats.hitRate()).isEqualTo(1.0); assertEquals(0, stats.missCount()); - assertEquals(0.0, stats.missRate()); + assertThat(stats.missRate()).isEqualTo(0.0); assertEquals(0, stats.loadSuccessCount()); assertEquals(0, stats.loadExceptionCount()); assertEquals(0, stats.loadCount()); assertEquals(0, stats.totalLoadTime()); - assertEquals(0.0, stats.averageLoadPenalty()); + assertThat(stats.averageLoadPenalty()).isEqualTo(0.0); assertEquals(0, stats.evictionCount()); } @@ -134,15 +139,15 @@ public void testSingleSimpleStats() { int requestCount = 11 + 23; assertEquals(requestCount, stats.requestCount()); assertEquals(11, stats.hitCount()); - assertEquals(11.0 / requestCount, stats.hitRate()); + assertThat(stats.hitRate()).isEqualTo(11.0 / requestCount); int missCount = 23; assertEquals(missCount, stats.missCount()); - assertEquals(((double) missCount) / requestCount, stats.missRate()); + assertThat(stats.missRate()).isEqualTo(((double) missCount) / requestCount); assertEquals(13, stats.loadSuccessCount()); assertEquals(17, stats.loadExceptionCount()); assertEquals(13 + 17, stats.loadCount()); assertEquals(214, stats.totalLoadTime()); - assertEquals(214.0 / (13 + 17), stats.averageLoadPenalty()); + assertThat(stats.averageLoadPenalty()).isEqualTo(214.0 / (13 + 17)); assertEquals(27, stats.evictionCount()); } 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..8d1c8a5ed19f 100644 --- a/android/guava-tests/test/com/google/common/cache/AbstractLoadingCacheTest.java +++ b/android/guava-tests/test/com/google/common/cache/AbstractLoadingCacheTest.java @@ -17,18 +17,22 @@ 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() { @@ -46,17 +50,14 @@ 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); @@ -78,17 +79,14 @@ 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); @@ -110,17 +108,14 @@ 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); @@ -142,17 +137,14 @@ 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); 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..d4a512adbfe8 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheBuilderFactory.java +++ b/android/guava-tests/test/com/google/common/cache/CacheBuilderFactory.java @@ -26,7 +26,8 @@ import java.util.List; 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 +35,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). @@ -88,7 +90,6 @@ CacheBuilderFactory withValueStrengths(Set valueStrengths) { } Iterable> buildAllPermutations() { - @SuppressWarnings("unchecked") Iterable> combinations = buildCartesianProduct( concurrencyLevels, @@ -120,15 +121,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 +159,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) { @@ -214,7 +215,7 @@ public int hashCode() { } @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..087cbb48980c 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,8 @@ package com.google.common.cache; +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 +31,8 @@ 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; /** * Test suite for {@link CacheBuilder}. TODO(cpovirk): merge into CacheBuilderTest? @@ -38,6 +40,7 @@ * @author Jon Donovan */ @GwtCompatible +@NullUnmarked public class CacheBuilderGwtTest extends TestCase { private FakeTicker fakeTicker; @@ -130,39 +133,33 @@ public Integer load(Integer key) throws Exception { public void testExpireAfterAccess() { final Cache cache = - CacheBuilder.newBuilder() - .expireAfterAccess(1000, TimeUnit.MILLISECONDS) - .ticker(fakeTicker) - .build(); + CacheBuilder.newBuilder().expireAfterAccess(1000, MILLISECONDS).ticker(fakeTicker).build(); cache.put(0, 10); cache.put(2, 30); - fakeTicker.advance(999, TimeUnit.MILLISECONDS); + fakeTicker.advance(999, MILLISECONDS); assertEquals(Integer.valueOf(30), cache.getIfPresent(2)); - fakeTicker.advance(1, TimeUnit.MILLISECONDS); + fakeTicker.advance(1, MILLISECONDS); assertEquals(Integer.valueOf(30), cache.getIfPresent(2)); - fakeTicker.advance(1000, TimeUnit.MILLISECONDS); + fakeTicker.advance(1000, MILLISECONDS); assertEquals(null, cache.getIfPresent(0)); } public void testExpireAfterWrite() { final Cache cache = - CacheBuilder.newBuilder() - .expireAfterWrite(1000, TimeUnit.MILLISECONDS) - .ticker(fakeTicker) - .build(); + 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); + fakeTicker.advance(999, MILLISECONDS); assertEquals(Integer.valueOf(100), cache.getIfPresent(10)); assertEquals(Integer.valueOf(200), cache.getIfPresent(20)); assertEquals(Integer.valueOf(2), cache.getIfPresent(4)); - fakeTicker.advance(2, TimeUnit.MILLISECONDS); + fakeTicker.advance(2, MILLISECONDS); assertEquals(null, cache.getIfPresent(10)); assertEquals(null, cache.getIfPresent(20)); assertEquals(null, cache.getIfPresent(4)); @@ -170,15 +167,15 @@ public void testExpireAfterWrite() { cache.put(10, 20); assertEquals(Integer.valueOf(20), cache.getIfPresent(10)); - fakeTicker.advance(1000, TimeUnit.MILLISECONDS); + fakeTicker.advance(1000, MILLISECONDS); assertEquals(null, cache.getIfPresent(10)); } public void testExpireAfterWriteAndAccess() { final Cache cache = CacheBuilder.newBuilder() - .expireAfterWrite(1000, TimeUnit.MILLISECONDS) - .expireAfterAccess(500, TimeUnit.MILLISECONDS) + .expireAfterWrite(1000, MILLISECONDS) + .expireAfterAccess(500, MILLISECONDS) .ticker(fakeTicker) .build(); @@ -186,23 +183,23 @@ public void testExpireAfterWriteAndAccess() { cache.put(20, 200); cache.put(4, 2); - fakeTicker.advance(499, TimeUnit.MILLISECONDS); + fakeTicker.advance(499, MILLISECONDS); assertEquals(Integer.valueOf(100), cache.getIfPresent(10)); assertEquals(Integer.valueOf(200), cache.getIfPresent(20)); - fakeTicker.advance(2, TimeUnit.MILLISECONDS); + fakeTicker.advance(2, MILLISECONDS); assertEquals(Integer.valueOf(100), cache.getIfPresent(10)); assertEquals(Integer.valueOf(200), cache.getIfPresent(20)); assertEquals(null, cache.getIfPresent(4)); - fakeTicker.advance(499, TimeUnit.MILLISECONDS); + fakeTicker.advance(499, MILLISECONDS); assertEquals(null, cache.getIfPresent(10)); assertEquals(null, cache.getIfPresent(20)); cache.put(10, 20); assertEquals(Integer.valueOf(20), cache.getIfPresent(10)); - fakeTicker.advance(500, TimeUnit.MILLISECONDS); + fakeTicker.advance(500, MILLISECONDS); assertEquals(null, cache.getIfPresent(10)); } @@ -276,7 +273,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 +293,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); @@ -371,17 +368,14 @@ public void testInvalidateAll() { public void testAsMap_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)); @@ -390,17 +384,14 @@ public void testAsMap_containsValue() { public void testAsMap_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)); @@ -409,17 +400,14 @@ public void testAsMap_containsKey() { public void testAsMapValues_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)); @@ -428,17 +416,14 @@ public void testAsMapValues_contains() { public void testAsMapKeySet() { 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()); @@ -447,17 +432,14 @@ public void testAsMapKeySet() { public void testAsMapKeySet_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)); @@ -466,17 +448,14 @@ public void testAsMapKeySet_contains() { public void testAsMapEntrySet() { 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()) { @@ -487,10 +466,7 @@ public void testAsMapEntrySet() { public void testAsMapValues_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(); 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..670275b08252 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,17 @@ import static com.google.common.cache.CacheBuilderSpec.parse; import static com.google.common.cache.TestingWeighers.constantWeigher; +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,6 +36,7 @@ * * @author Adam Winer */ +@NullUnmarked public class CacheBuilderSpecTest extends TestCase { public void testParse_empty() { CacheBuilderSpec spec = parse(""); @@ -60,11 +66,8 @@ public void testParse_initialCapacity() { } 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() { @@ -81,11 +84,7 @@ public void testParse_maximumSize() { } 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() { @@ -102,19 +101,11 @@ public void testParse_maximumWeight() { } 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() { @@ -132,11 +123,8 @@ public void testParse_concurrencyLevel() { } 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() { @@ -153,19 +141,11 @@ public void testParse_weakKeys() { } 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() { @@ -182,11 +162,7 @@ public void testParse_softValues() { } public void testParse_softValuesCannotHaveValue() { - try { - parse("softValues=true"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("softValues=true")); } public void testParse_weakValues() { @@ -203,37 +179,17 @@ public void testParse_weakValues() { } 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() { @@ -244,43 +200,40 @@ public void testParse_writeExpirationDays() { assertNull(spec.concurrencyLevel); assertNull(spec.keyStrength); assertNull(spec.valueStrength); - assertEquals(TimeUnit.DAYS, spec.writeExpirationTimeUnit); + assertEquals(DAYS, spec.writeExpirationTimeUnit); assertEquals(10L, spec.writeExpirationDuration); assertNull(spec.accessExpirationTimeUnit); 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(HOURS, spec.writeExpirationTimeUnit); assertEquals(150L, spec.writeExpirationDuration); 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(MINUTES, spec.writeExpirationTimeUnit); assertEquals(10L, spec.writeExpirationDuration); 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(SECONDS, spec.writeExpirationTimeUnit); assertEquals(10L, spec.writeExpirationDuration); 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() { @@ -292,44 +245,39 @@ public void testParse_accessExpirationDays() { assertNull(spec.keyStrength); assertNull(spec.valueStrength); assertNull(spec.writeExpirationTimeUnit); - assertEquals(TimeUnit.DAYS, spec.accessExpirationTimeUnit); + assertEquals(DAYS, spec.accessExpirationTimeUnit); assertEquals(10L, spec.accessExpirationDuration); 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(HOURS, spec.accessExpirationTimeUnit); assertEquals(150L, spec.accessExpirationDuration); 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(MINUTES, spec.accessExpirationTimeUnit); assertEquals(10L, spec.accessExpirationDuration); 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(SECONDS, spec.accessExpirationTimeUnit); assertEquals(10L, spec.accessExpirationDuration); 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() { @@ -339,31 +287,21 @@ public void testParse_recordStats() { } 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(MINUTES, spec.writeExpirationTimeUnit); assertEquals(9L, spec.writeExpirationDuration); - assertEquals(TimeUnit.SECONDS, spec.accessExpirationTimeUnit); + assertEquals(SECONDS, spec.accessExpirationTimeUnit); assertEquals(10L, spec.accessExpirationDuration); assertCacheBuilderEquivalence( - CacheBuilder.newBuilder() - .expireAfterAccess(10L, TimeUnit.SECONDS) - .expireAfterWrite(9L, TimeUnit.MINUTES), + CacheBuilder.newBuilder().expireAfterAccess(10L, SECONDS).expireAfterWrite(9L, MINUTES), CacheBuilder.from(spec)); } @@ -378,8 +316,8 @@ public void testParse_multipleKeys() { 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(HOURS, spec.writeExpirationTimeUnit); + assertEquals(MINUTES, spec.accessExpirationTimeUnit); assertEquals(1L, spec.writeExpirationDuration); assertEquals(10L, spec.accessExpirationDuration); CacheBuilder expected = @@ -389,8 +327,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)); } @@ -405,7 +343,7 @@ public void testParse_whitespaceAllowed() { assertNull(spec.concurrencyLevel); assertEquals(Strength.WEAK, spec.keyStrength); assertEquals(Strength.SOFT, spec.valueStrength); - assertEquals(TimeUnit.SECONDS, spec.writeExpirationTimeUnit); + assertEquals(SECONDS, spec.writeExpirationTimeUnit); assertEquals(15L, spec.writeExpirationDuration); assertNull(spec.accessExpirationTimeUnit); CacheBuilder expected = @@ -414,36 +352,20 @@ public void testParse_whitespaceAllowed() { .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 +399,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") @@ -521,7 +441,7 @@ public void testCacheBuilderFrom_string() { .concurrencyLevel(30) .weakKeys() .weakValues() - .expireAfterAccess(10L, TimeUnit.MINUTES); + .expireAfterAccess(10L, MINUTES); assertCacheBuilderEquivalence(expected, fromString); } 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..45314e0b85aa 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; @@ -35,6 +36,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.testing.NullPointerTester; +import java.time.Duration; import java.util.Map; import java.util.Random; import java.util.Set; @@ -45,9 +47,13 @@ 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) +// We are intentionally testing the TimeUnit overloads, too. +@SuppressWarnings("LongTimeUnit_ExpireAfterWrite_Seconds") +@NullUnmarked public class CacheBuilderTest extends TestCase { public void testNewBuilder() { @@ -62,21 +68,12 @@ public void testNewBuilder() { 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 @@ -112,21 +109,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 +132,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 +157,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 +238,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,65 +269,61 @@ 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() { @@ -399,7 +344,6 @@ public void testNullCache() { } @GwtIncompatible // QueuingRemovalListener - public void testRemovalNotification_clear() throws InterruptedException { // If a clear() happens while a computation is pending, we should not get a removal // notification. @@ -471,6 +415,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 @@ -547,6 +492,8 @@ public void run() { // notification. assertEquals(expectedKeys, Sets.union(cache.asMap().keySet(), removalNotifications.keySet())); assertTrue(Sets.intersection(cache.asMap().keySet(), removalNotifications.keySet()).isEmpty()); + threadPool.shutdown(); + threadPool.awaitTermination(300, SECONDS); } /** @@ -566,6 +513,7 @@ public void testRemovalNotification_get_basher() throws InterruptedException { final AtomicInteger computeCount = new AtomicInteger(); final AtomicInteger exceptionCount = new AtomicInteger(); final AtomicInteger computeNullCount = new AtomicInteger(); + @SuppressWarnings("CacheLoaderNull") // test of handling of erroneous implementation CacheLoader countingIdentityLoader = new CacheLoader() { @Override 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..1f0d7b41f650 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; @@ -61,7 +64,7 @@ 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()); + assertEquals(min(i + 1, MAX_SIZE), cache.size()); } assertEquals(MAX_SIZE, cache.size()); @@ -78,7 +81,7 @@ 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()); + assertEquals(min(i + 1, MAX_SIZE), cache.size()); } assertEquals(MAX_SIZE, cache.size()); 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..86d3e4af648c 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheExpirationTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheExpirationTest.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; 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 +30,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 +41,7 @@ * @author mike nonemacher */ @SuppressWarnings("deprecation") // tests of deprecated method +@NullUnmarked public class CacheExpirationTest extends TestCase { private static final long EXPIRING_TIME = 1000; @@ -339,7 +341,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 +405,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 +417,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); 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..9c8cd2819658 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheLoaderTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheLoaderTest.java @@ -16,10 +16,11 @@ package com.google.common.cache; +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.Deque; import java.util.Map; @@ -27,12 +28,14 @@ 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 { @@ -64,7 +67,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 @@ -78,10 +81,10 @@ public Map loadAll(Iterable keys) { assertEquals(0, reloadCount.get()); assertEquals(0, loadAllCount.get()); - 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())); + Map unused2 = baseLoader.loadAll(ImmutableList.of(new Object())); assertEquals(1, loadCount.get()); assertEquals(1, reloadCount.get()); assertEquals(1, loadAllCount.get()); @@ -89,10 +92,10 @@ public Map loadAll(Iterable keys) { 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())); + Map unused4 = asyncReloader.loadAll(ImmutableList.of(new Object())); assertEquals(2, loadCount.get()); assertEquals(1, reloadCount.get()); assertEquals(2, loadAllCount.get()); 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..cd37da76bfba 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheLoadingTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheLoadingTest.java @@ -21,9 +21,12 @@ 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.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; @@ -37,7 +40,6 @@ 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; @@ -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,7 +77,7 @@ 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); } @@ -169,7 +172,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFuture(two); + return immediateFuture(two); } }; @@ -217,7 +220,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFuture(two); + return immediateFuture(two); } }; @@ -279,7 +282,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFuture(two); + return immediateFuture(two); } }; @@ -521,11 +524,7 @@ public Map loadAll(Iterable keys) throws Exception { assertSame(extraKey, cache.asMap().get(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)); @@ -561,11 +560,7 @@ public Map loadAll(Iterable keys) throws Exception { assertSame(extraKey, cache.asMap().get(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)); @@ -595,11 +590,7 @@ 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) { - } + assertThrows(InvalidCacheLoadException.class, () -> cache.getAll(asList(lookupKeys))); assertSame(extraValue, cache.asMap().get(extraKey)); } @@ -612,22 +603,14 @@ public void testLoadNull() throws ExecutionException { assertEquals(0, stats.loadExceptionCount()); assertEquals(0, stats.hitCount()); - try { - cache.get(new Object()); - fail(); - } catch (InvalidCacheLoadException expected) { - } + 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()); - 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()); @@ -642,22 +625,15 @@ public void testLoadNull() throws ExecutionException { assertEquals(3, stats.loadExceptionCount()); assertEquals(0, stats.hitCount()); - 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()); - 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()); @@ -722,7 +698,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFuture(null); + return immediateFuture(null); } }; @@ -769,7 +745,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFuture(null); + return immediateFuture(null); } }; @@ -828,11 +804,7 @@ public void testBulkLoadNull() throws ExecutionException { assertEquals(0, stats.loadExceptionCount()); assertEquals(0, stats.hitCount()); - try { - cache.getAll(asList(new Object())); - fail(); - } catch (InvalidCacheLoadException expected) { - } + assertThrows(InvalidCacheLoadException.class, () -> cache.getAll(asList(new Object()))); stats = cache.stats(); assertEquals(1, stats.missCount()); assertEquals(0, stats.loadSuccessCount()); @@ -863,11 +835,7 @@ public Map loadAll(Iterable keys) { assertEquals(0, stats.loadExceptionCount()); assertEquals(0, stats.hitCount()); - try { - cache.getAll(asList(new Object())); - fail(); - } catch (InvalidCacheLoadException expected) { - } + assertThrows(InvalidCacheLoadException.class, () -> cache.getAll(asList(new Object()))); stats = cache.stats(); assertEquals(1, stats.missCount()); assertEquals(0, stats.loadSuccessCount()); @@ -885,24 +853,16 @@ public void testLoadError() throws ExecutionException { assertEquals(0, stats.loadExceptionCount()); assertEquals(0, stats.hitCount()); - try { - cache.get(new Object()); - fail(); - } catch (ExecutionError expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } + 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()); - 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()); @@ -918,31 +878,27 @@ public void testLoadError() throws ExecutionException { 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); - } + expected = + assertThrows( + ExecutionError.class, + () -> + cache.get( + new Object(), + new Callable() { + @Override + public Object call() { + throw callableError; + } + })); + 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); - } + expected = assertThrows(ExecutionError.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); stats = cache.stats(); assertEquals(4, stats.missCount()); assertEquals(0, stats.loadSuccessCount()); @@ -1009,7 +965,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFailedFuture(e); + return immediateFailedFuture(e); } }; @@ -1057,7 +1013,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFailedFuture(e); + return immediateFailedFuture(e); } }; @@ -1118,12 +1074,9 @@ public void testBulkLoadError() throws ExecutionException { assertEquals(0, stats.loadExceptionCount()); assertEquals(0, stats.hitCount()); - try { - cache.getAll(asList(new Object())); - fail(); - } catch (ExecutionError expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } + ExecutionError expected = + assertThrows(ExecutionError.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); stats = cache.stats(); assertEquals(1, stats.missCount()); assertEquals(0, stats.loadSuccessCount()); @@ -1141,24 +1094,17 @@ public void testLoadCheckedException() { assertEquals(0, stats.loadExceptionCount()); assertEquals(0, stats.hitCount()); - try { - cache.get(new Object()); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } + 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()); - 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()); @@ -1174,24 +1120,18 @@ public void testLoadCheckedException() { assertEquals(0, stats.hitCount()); Exception callableException = new Exception(); - try { - cache.get(new Object(), throwing(callableException)); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(callableException); - } + expected = + assertThrows( + ExecutionException.class, () -> cache.get(new Object(), throwing(callableException))); + 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); - } + expected = assertThrows(ExecutionException.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); stats = cache.stats(); assertEquals(4, stats.missCount()); assertEquals(0, stats.loadSuccessCount()); @@ -1210,28 +1150,21 @@ public void testLoadInterruptedException() { assertEquals(0, stats.hitCount()); // Sanity check: - assertFalse(currentThread().interrupted()); + assertFalse(Thread.interrupted()); - try { - cache.get(new Object()); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } - assertTrue(currentThread().interrupted()); + Exception expected = assertThrows(ExecutionException.class, () -> cache.get(new Object())); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + assertTrue(Thread.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()); + expected = + assertThrows(UncheckedExecutionException.class, () -> cache.getUnchecked(new Object())); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + assertTrue(Thread.interrupted()); stats = cache.stats(); assertEquals(2, stats.missCount()); assertEquals(0, stats.loadSuccessCount()); @@ -1239,7 +1172,7 @@ public void testLoadInterruptedException() { assertEquals(0, stats.hitCount()); cache.refresh(new Object()); - assertTrue(currentThread().interrupted()); + assertTrue(Thread.interrupted()); checkLoggedCause(e); stats = cache.stats(); assertEquals(2, stats.missCount()); @@ -1248,26 +1181,20 @@ public void testLoadInterruptedException() { assertEquals(0, stats.hitCount()); Exception callableException = new InterruptedException(); - try { - cache.get(new Object(), throwing(callableException)); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(callableException); - } - assertTrue(currentThread().interrupted()); + expected = + assertThrows( + ExecutionException.class, () -> cache.get(new Object(), throwing(callableException))); + assertThat(expected).hasCauseThat().isSameInstanceAs(callableException); + assertTrue(Thread.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()); + expected = assertThrows(ExecutionException.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + assertTrue(Thread.interrupted()); stats = cache.stats(); assertEquals(4, stats.missCount()); assertEquals(0, stats.loadSuccessCount()); @@ -1334,7 +1261,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFailedFuture(e); + return immediateFailedFuture(e); } }; @@ -1382,7 +1309,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFailedFuture(e); + return immediateFailedFuture(e); } }; @@ -1443,12 +1370,9 @@ public void testBulkLoadCheckedException() { assertEquals(0, stats.loadExceptionCount()); assertEquals(0, stats.hitCount()); - try { - cache.getAll(asList(new Object())); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); stats = cache.stats(); assertEquals(1, stats.missCount()); assertEquals(0, stats.loadSuccessCount()); @@ -1467,13 +1391,10 @@ public void testBulkLoadInterruptedException() { 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()); + ExecutionException expected = + assertThrows(ExecutionException.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + assertTrue(Thread.interrupted()); stats = cache.stats(); assertEquals(1, stats.missCount()); assertEquals(0, stats.loadSuccessCount()); @@ -1491,24 +1412,18 @@ public void testLoadUncheckedException() throws ExecutionException { assertEquals(0, stats.loadExceptionCount()); assertEquals(0, stats.hitCount()); - try { - cache.get(new Object()); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } + 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()); - 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()); @@ -1524,24 +1439,20 @@ public void testLoadUncheckedException() throws ExecutionException { assertEquals(0, stats.hitCount()); Exception callableException = new RuntimeException(); - try { - cache.get(new Object(), throwing(callableException)); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(callableException); - } + expected = + assertThrows( + UncheckedExecutionException.class, + () -> cache.get(new Object(), throwing(callableException))); + 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); - } + expected = + assertThrows(UncheckedExecutionException.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); stats = cache.stats(); assertEquals(4, stats.missCount()); assertEquals(0, stats.loadSuccessCount()); @@ -1608,7 +1519,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFailedFuture(e); + return immediateFailedFuture(e); } }; @@ -1656,7 +1567,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFailedFuture(e); + return immediateFailedFuture(e); } }; @@ -1717,12 +1628,9 @@ public void testBulkLoadUncheckedException() throws ExecutionException { assertEquals(0, stats.loadExceptionCount()); assertEquals(0, stats.hitCount()); - try { - cache.getAll(asList(new Object())); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } + UncheckedExecutionException expected = + assertThrows(UncheckedExecutionException.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); stats = cache.stats(); assertEquals(1, stats.missCount()); assertEquals(0, stats.loadSuccessCount()); @@ -1748,12 +1656,9 @@ 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()); @@ -1767,6 +1672,7 @@ public String load(Integer key) throws Exception { } + @AndroidIncompatible // Depends on GC behavior public void testReloadAfterValueReclamation() throws InterruptedException, ExecutionException { CountingLoader countingLoader = new CountingLoader(); LoadingCache cache = @@ -1861,57 +1767,38 @@ public void testLoadingExceptionWithCause() { 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() { @@ -1924,24 +1811,17 @@ public void testBulkLoadingExceptionWithCause() { 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)); } /** @@ -2006,6 +1886,7 @@ private static void testConcurrentLoadingNull(CacheBuilder build builder.build( new CacheLoader() { @Override + @SuppressWarnings("CacheLoaderNull") // test of broken user implementation public String load(String key) throws InterruptedException { callCount.incrementAndGet(); startSignal.await(); @@ -2132,6 +2013,7 @@ 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, @@ -2184,7 +2066,6 @@ public void run() { return resultList; } - public void testAsMapDuringLoading() throws InterruptedException, ExecutionException { final CountDownLatch getStartedSignal = new CountDownLatch(2); final CountDownLatch letGetFinishSignal = new CountDownLatch(1); @@ -2243,7 +2124,6 @@ public void run() { assertEquals(refreshKey + suffix, map.get(refreshKey)); } - public void testInvalidateDuringLoading() throws InterruptedException, ExecutionException { // computation starts; invalidate() is called on the key being computed, computation finishes final CountDownLatch computationStarted = new CountDownLatch(2); @@ -2300,7 +2180,6 @@ public void run() { assertEquals(2, cache.size()); } - public void testInvalidateAndReloadDuringLoading() throws InterruptedException, ExecutionException { // computation starts; clear() is called, computation finishes @@ -2373,7 +2252,7 @@ public void run() { assertEquals(refreshKey + suffix, map.get(refreshKey)); } - + @SuppressWarnings("ThreadPriorityCheck") // doing our best to test for races public void testExpandDuringLoading() throws InterruptedException { final int count = 3; final AtomicInteger callCount = new AtomicInteger(); @@ -2463,7 +2342,7 @@ public void run() { } // 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 { 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..a2cf24dcef28 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheManualTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheManualTest.java @@ -19,8 +19,12 @@ 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() { 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..a11d14a090fd 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheReferencesTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheReferencesTest.java @@ -17,6 +17,7 @@ import static com.google.common.cache.LocalCache.Strength.STRONG; 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.base.Function; import com.google.common.cache.LocalCache.Strength; @@ -25,6 +26,7 @@ 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 +34,7 @@ * * @author mike nonemacher */ +@NullUnmarked public class CacheReferencesTest extends TestCase { private static final CacheLoader KEY_TO_STRING_LOADER = @@ -120,7 +123,7 @@ public void testInvalidate() { } } - // fails in Maven with 64-bit JDK: http://code.google.com/p/guava-libraries/issues/detail?id=1568 + // fails in Maven with 64-bit JDK: https://github.com/google/guava/issues/1568 private void assertCleanup( LoadingCache cache, @@ -147,7 +150,7 @@ private void assertCleanup( } try { // Fill up heap so soft references get cleared. - filler = new byte[Math.max(filler.length, filler.length * 2)]; + filler = new byte[max(filler.length, filler.length * 2)]; } catch (OutOfMemoryError e) { } } 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..6a7948c470dc 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheRefreshTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheRefreshTest.java @@ -20,12 +20,14 @@ 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(); 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..cfd174aea242 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheStatsTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheStatsTest.java @@ -16,28 +16,32 @@ 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()); + assertThat(stats.hitRate()).isEqualTo(1.0); assertEquals(0, stats.missCount()); - assertEquals(0.0, stats.missRate()); + assertThat(stats.missRate()).isEqualTo(0.0); assertEquals(0, stats.loadSuccessCount()); assertEquals(0, stats.loadExceptionCount()); - assertEquals(0.0, stats.loadExceptionRate()); + assertThat(stats.loadExceptionRate()).isEqualTo(0.0); assertEquals(0, stats.loadCount()); assertEquals(0, stats.totalLoadTime()); - assertEquals(0.0, stats.averageLoadPenalty()); + assertThat(stats.averageLoadPenalty()).isEqualTo(0.0); assertEquals(0, stats.evictionCount()); } @@ -45,15 +49,15 @@ 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()); + assertThat(stats.hitRate()).isEqualTo(11.0 / 24); assertEquals(13, stats.missCount()); - assertEquals(13.0 / 24, stats.missRate()); + assertThat(stats.missRate()).isEqualTo(13.0 / 24); assertEquals(17, stats.loadSuccessCount()); assertEquals(19, stats.loadExceptionCount()); - assertEquals(19.0 / 36, stats.loadExceptionRate()); + assertThat(stats.loadExceptionRate()).isEqualTo(19.0 / 36); assertEquals(17 + 19, stats.loadCount()); assertEquals(23, stats.totalLoadTime()); - assertEquals(23.0 / (17 + 19), stats.averageLoadPenalty()); + assertThat(stats.averageLoadPenalty()).isEqualTo(23.0 / (17 + 19)); assertEquals(27, stats.evictionCount()); } @@ -64,15 +68,15 @@ public void testMinus() { CacheStats diff = two.minus(one); assertEquals(76, diff.requestCount()); assertEquals(42, diff.hitCount()); - assertEquals(42.0 / 76, diff.hitRate()); + assertThat(diff.hitRate()).isEqualTo(42.0 / 76); assertEquals(34, diff.missCount()); - assertEquals(34.0 / 76, diff.missRate()); + assertThat(diff.missRate()).isEqualTo(34.0 / 76); assertEquals(26, diff.loadSuccessCount()); assertEquals(22, diff.loadExceptionCount()); - assertEquals(22.0 / 48, diff.loadExceptionRate()); + assertThat(diff.loadExceptionRate()).isEqualTo(22.0 / 48); assertEquals(26 + 22, diff.loadCount()); assertEquals(14, diff.totalLoadTime()); - assertEquals(14.0 / (26 + 22), diff.averageLoadPenalty()); + assertThat(diff.averageLoadPenalty()).isEqualTo(14.0 / (26 + 22)); assertEquals(4, diff.evictionCount()); assertEquals(new CacheStats(0, 0, 0, 0, 0, 0), one.minus(two)); @@ -85,15 +89,15 @@ public void testPlus() { CacheStats sum = two.plus(one); assertEquals(124, sum.requestCount()); assertEquals(64, sum.hitCount()); - assertEquals(64.0 / 124, sum.hitRate()); + assertThat(sum.hitRate()).isEqualTo(64.0 / 124); assertEquals(60, sum.missCount()); - assertEquals(60.0 / 124, sum.missRate()); + assertThat(sum.missRate()).isEqualTo(60.0 / 124); assertEquals(56, sum.loadSuccessCount()); assertEquals(52, sum.loadExceptionCount()); - assertEquals(52.0 / 108, sum.loadExceptionRate()); + assertThat(sum.loadExceptionRate()).isEqualTo(52.0 / 108); assertEquals(56 + 52, sum.loadCount()); assertEquals(48, sum.totalLoadTime()); - assertEquals(48.0 / (56 + 52), sum.averageLoadPenalty()); + assertThat(sum.averageLoadPenalty()).isEqualTo(48.0 / (56 + 52)); assertEquals(44, sum.evictionCount()); assertEquals(sum, one.plus(two)); @@ -113,15 +117,15 @@ public void testPlusLarge() { CacheStats sum = smallCacheStats.plus(maxCacheStats); assertEquals(Long.MAX_VALUE, sum.requestCount()); assertEquals(Long.MAX_VALUE, sum.hitCount()); - assertEquals(1.0, sum.hitRate()); + assertThat(sum.hitRate()).isEqualTo(1.0); assertEquals(Long.MAX_VALUE, sum.missCount()); - assertEquals(1.0, sum.missRate()); + assertThat(sum.missRate()).isEqualTo(1.0); assertEquals(Long.MAX_VALUE, sum.loadSuccessCount()); assertEquals(Long.MAX_VALUE, sum.loadExceptionCount()); - assertEquals(1.0, sum.loadExceptionRate()); + assertThat(sum.loadExceptionRate()).isEqualTo(1.0); assertEquals(Long.MAX_VALUE, sum.loadCount()); assertEquals(Long.MAX_VALUE, sum.totalLoadTime()); - assertEquals(1.0, sum.averageLoadPenalty()); + assertThat(sum.averageLoadPenalty()).isEqualTo(1.0); assertEquals(Long.MAX_VALUE, sum.evictionCount()); assertEquals(sum, maxCacheStats.plus(smallCacheStats)); 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..3156e409ca29 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheTesting.java +++ b/android/guava-tests/test/com/google/common/cache/CacheTesting.java @@ -16,6 +16,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; +import static java.lang.Math.max; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -42,9 +44,9 @@ 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,6 +54,7 @@ * @author mike nonemacher */ @SuppressWarnings("GuardedBy") // TODO(b/35466881): Fix or suppress. +@NullUnmarked class CacheTesting { /** @@ -79,7 +82,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 +100,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); @@ -356,7 +358,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 +369,7 @@ static void processPendingNotifications(Cache cache) { } interface Receiver { - void accept(@NullableDecl T object); + void accept(@Nullable T object); } /** @@ -421,7 +423,7 @@ 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) { 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..f93568bc29a5 100644 --- a/android/guava-tests/test/com/google/common/cache/EmptyCachesTest.java +++ b/android/guava-tests/test/com/google/common/cache/EmptyCachesTest.java @@ -19,6 +19,7 @@ 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 +33,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 +41,7 @@ * @author mike nonemacher */ +@NullUnmarked public class EmptyCachesTest extends TestCase { public void testEmpty() { @@ -89,22 +92,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 +109,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))); } } @@ -189,28 +173,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))); } } @@ -264,28 +237,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)))); } } 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..76de76c3707c 100644 --- a/android/guava-tests/test/com/google/common/cache/ForwardingCacheTest.java +++ b/android/guava-tests/test/com/google/common/cache/ForwardingCacheTest.java @@ -24,12 +24,14 @@ import com.google.common.collect.ImmutableMap; 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; @@ -104,7 +106,7 @@ public void testCleanUp() { 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..cdef91afe699 100644 --- a/android/guava-tests/test/com/google/common/cache/ForwardingLoadingCacheTest.java +++ b/android/guava-tests/test/com/google/common/cache/ForwardingLoadingCacheTest.java @@ -24,12 +24,14 @@ import com.google.common.collect.ImmutableMap; 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; @@ -112,7 +114,7 @@ public void testCleanUp() { 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..e5c8be9ba5cc 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,13 @@ 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.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,6 +51,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.testing.ConcurrentMapTestSuiteBuilder; @@ -58,9 +63,14 @@ 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.List; @@ -76,9 +86,14 @@ 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 { private static class TestStringCacheGenerator extends TestStringMapGenerator { private final CacheBuilder builder; @@ -97,7 +112,6 @@ protected Map create(Entry[] entries) { } } - public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(LocalCacheTest.class); @@ -232,7 +246,7 @@ public void tearDown() throws Exception { private Throwable popLoggedThrowable() { List logRecords = logHandler.getStoredLogRecords(); - assertSame(1, logRecords.size()); + assertEquals(1, logRecords.size()); LogRecord logRecord = logRecords.get(0); logHandler.clear(); return logRecord.getThrown(); @@ -246,11 +260,24 @@ private void checkLogged(Throwable t) { assertSame(t, popLoggedThrowable()); } + /* + * 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(); } @@ -330,7 +357,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); @@ -349,7 +376,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); @@ -429,7 +456,7 @@ private static void checkMaximumSize(int concurrencyLevel, int initialCapacity, long totalCapacity = 0; assertTrue( "segments=" + map.segments.length + ", maxSize=" + maxSize, - map.segments.length <= Math.max(1, maxSize / 10)); + map.segments.length <= max(1, maxSize / 10)); for (int i = 0; i < map.segments.length; i++) { totalCapacity += map.segments[i].maxSegmentWeight; } @@ -444,7 +471,7 @@ private static void checkMaximumSize(int concurrencyLevel, int initialCapacity, .weigher(constantWeigher(1))); assertTrue( "segments=" + map.segments.length + ", maxSize=" + maxSize, - map.segments.length <= Math.max(1, maxSize / 10)); + map.segments.length <= max(1, maxSize / 10)); totalCapacity = 0; for (int i = 0; i < map.segments.length; i++) { totalCapacity += map.segments[i].maxSegmentWeight; @@ -493,7 +520,7 @@ private static void checkStrength( 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); @@ -501,7 +528,7 @@ public void testSetExpireAfterWrite() { 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); @@ -509,12 +536,64 @@ public void testSetExpireAfterAccess() { 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); } + 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 = @@ -583,7 +662,7 @@ public void testRecordReadOnCompute() throws ExecutionException { // 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(); @@ -676,8 +755,7 @@ public void testComputePartiallyCollectedValue() throws ExecutionException { @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()); @@ -701,7 +779,6 @@ public void testValues() { assertEquals(1, map.size()); } - public void testCopyEntry_computing() { final CountDownLatch startSignal = new CountDownLatch(1); final CountDownLatch computingSignal = new CountDownLatch(1); @@ -803,7 +880,6 @@ public void onRemoval(RemovalNotification notification) { checkLogged(e); } - public void testRemovalListener_replaced_computing() { final CountDownLatch startSignal = new CountDownLatch(1); final CountDownLatch computingSignal = new CountDownLatch(1); @@ -980,7 +1056,7 @@ public void testRemovalListener_expired() { makeLocalCache( createCacheBuilder() .concurrencyLevel(1) - .expireAfterWrite(3, TimeUnit.NANOSECONDS) + .expireAfterWrite(3, NANOSECONDS) .ticker(ticker) .removalListener(listener)); assertTrue(listener.isEmpty()); @@ -1123,7 +1199,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 @@ -1888,7 +1964,7 @@ public void testRemoveEntry() { table.set(0, entry); segment.count = 1; assertTrue(segment.removeEntry(entry, hash, RemovalCause.COLLECTED)); - assertNotificationEnqueued(map, key, value, hash); + assertNotificationEnqueued(map, key, value); assertTrue(map.removalNotificationQueue.isEmpty()); assertFalse(segment.accessQueue.contains(entry)); assertFalse(segment.writeQueue.contains(entry)); @@ -1997,8 +2073,7 @@ public void testRemoveComputingValue() { assertTrue(segment.removeLoadingValue(key, hash, valueRef)); } - 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()); @@ -2098,7 +2173,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(); @@ -2139,7 +2214,7 @@ public void testRecordReadOnGet() { // 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(); @@ -2180,7 +2255,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(); @@ -2269,7 +2344,7 @@ public void testExpireAfterWrite() { createCacheBuilder() .concurrencyLevel(1) .ticker(ticker) - .expireAfterWrite(2, TimeUnit.NANOSECONDS)); + .expireAfterWrite(2, NANOSECONDS)); Segment segment = map.segments[0]; Object key = new Object(); @@ -2310,7 +2385,7 @@ public void testExpireAfterAccess() { createCacheBuilder() .concurrencyLevel(1) .ticker(ticker) - .expireAfterAccess(2, TimeUnit.NANOSECONDS)); + .expireAfterAccess(2, NANOSECONDS)); Segment segment = map.segments[0]; Object key = new Object(); @@ -2524,7 +2599,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() { @@ -2641,15 +2716,93 @@ public void testSerializationProxyManual() { assertEquals(localCacheTwo.ticker, localCacheThree.ticker); } + public void testLoadDifferentKeyInLoader() throws ExecutionException, InterruptedException { + LocalCache cache = makeLocalCache(createCacheBuilder()); + String key1 = "key1"; + String key2 = "key2"; + + assertEquals( + key2, + cache.get( + key1, + new CacheLoader() { + @Override + public String load(String key) throws Exception { + return cache.get(key2, identityLoader()); // loads a different key, should work + } + })); + } + + 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 +2822,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 +2845,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 +2853,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 +2863,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 +2973,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 +3003,7 @@ public int getWeight() { } @Override - public ReferenceEntry getEntry() { + public @Nullable ReferenceEntry getEntry() { return null; } @@ -2905,7 +3053,7 @@ public int hashCode() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return (o instanceof SerializableCacheLoader); } } @@ -2921,7 +3069,7 @@ public int hashCode() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return (o instanceof SerializableRemovalListener); } } @@ -2938,7 +3086,7 @@ public int hashCode() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return (o instanceof SerializableTicker); } } @@ -2955,7 +3103,7 @@ public int hashCode() { } @Override - public boolean equals(Object o) { + 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..8111b2e6ad00 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( @@ -81,9 +85,9 @@ public void testStats() { CacheStats stats = cache.stats(); assertEquals(1, stats.requestCount()); assertEquals(0, stats.hitCount()); - assertEquals(0.0, stats.hitRate()); + assertThat(stats.hitRate()).isEqualTo(0.0); assertEquals(1, stats.missCount()); - assertEquals(1.0, stats.missRate()); + assertThat(stats.missRate()).isEqualTo(1.0); assertEquals(1, stats.loadCount()); long totalLoadTime = stats.totalLoadTime(); assertTrue(totalLoadTime >= 0); @@ -94,9 +98,9 @@ public void testStats() { stats = cache.stats(); assertEquals(2, stats.requestCount()); assertEquals(1, stats.hitCount()); - assertEquals(1.0 / 2, stats.hitRate()); + assertThat(stats.hitRate()).isEqualTo(1.0 / 2); assertEquals(1, stats.missCount()); - assertEquals(1.0 / 2, stats.missRate()); + assertThat(stats.missRate()).isEqualTo(1.0 / 2); assertEquals(1, stats.loadCount()); assertEquals(0, stats.evictionCount()); @@ -105,9 +109,9 @@ public void testStats() { stats = cache.stats(); assertEquals(3, stats.requestCount()); assertEquals(1, stats.hitCount()); - assertEquals(1.0 / 3, stats.hitRate()); + assertThat(stats.hitRate()).isEqualTo(1.0 / 3); assertEquals(2, stats.missCount()); - assertEquals(2.0 / 3, stats.missRate()); + assertThat(stats.missRate()).isEqualTo(2.0 / 3); assertEquals(2, stats.loadCount()); assertTrue(stats.totalLoadTime() >= totalLoadTime); totalLoadTime = stats.totalLoadTime(); @@ -119,12 +123,11 @@ public void testStats() { stats = cache.stats(); assertEquals(4, stats.requestCount()); assertEquals(1, stats.hitCount()); - assertEquals(1.0 / 4, stats.hitRate()); + assertThat(stats.hitRate()).isEqualTo(1.0 / 4); assertEquals(3, stats.missCount()); - assertEquals(3.0 / 4, stats.missRate()); + assertThat(stats.missRate()).isEqualTo(3.0 / 4); assertEquals(3, stats.loadCount()); assertTrue(stats.totalLoadTime() >= totalLoadTime); - totalLoadTime = stats.totalLoadTime(); assertTrue(stats.averageLoadPenalty() >= 0.0); assertEquals(1, stats.evictionCount()); } @@ -157,7 +160,7 @@ public void testStatsNoops() { 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)); @@ -293,7 +296,6 @@ public void testAsMapRecency() { assertFalse(segment.recencyQueue.isEmpty()); } - public void testRecursiveComputation() throws InterruptedException { final AtomicReference> cacheRef = new AtomicReference<>(); CacheLoader recursiveLoader = @@ -324,7 +326,7 @@ public String load(Integer key) { recursiveCache = CacheBuilder.newBuilder().weakKeys().weakValues().build(recursiveLoader); cacheRef.set(recursiveCache); - // tells the test when the compution has completed + // tells the test when the computation has completed final CountDownLatch doneSignal = new CountDownLatch(1); Thread 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..23ccbe9ff0eb 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; @@ -100,12 +103,7 @@ 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()); checkEmpty(cache); @@ -119,12 +117,9 @@ public void testGet_runtimeException() { .removalListener(listener) .build(exceptionLoader(e)); - try { - map.getUnchecked(new Object()); - fail(); - } catch (UncheckedExecutionException uee) { - assertThat(uee).hasCauseThat().isSameInstanceAs(e); - } + UncheckedExecutionException uee = + assertThrows(UncheckedExecutionException.class, () -> map.getUnchecked(new Object())); + assertThat(uee).hasCauseThat().isSameInstanceAs(e); assertTrue(listener.isEmpty()); 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..81eea6c63ff3 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; @@ -37,6 +38,7 @@ 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 +46,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,7 +57,7 @@ public class PopulatedCachesTest extends TestCase { public void testSize_populated() { for (LoadingCache cache : caches()) { // don't let the entries get GCed - List> warmed = warmUp(cache); + List> unused = warmUp(cache); assertEquals(WARMUP_SIZE, cache.size()); assertMapSize(cache.asMap(), WARMUP_SIZE); checkValidState(cache); @@ -124,7 +127,7 @@ 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)); @@ -279,11 +282,7 @@ public void testWriteThroughEntry() { assertEquals(3, cache.getIfPresent(1)); checkValidState(cache); - try { - entry.setValue(null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> entry.setValue(null)); checkValidState(cache); } } 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..c29f754b140b --- /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(emulated = true) +@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..5479550a8af2 100644 --- a/android/guava-tests/test/com/google/common/cache/TestingCacheLoaders.java +++ b/android/guava-tests/test/com/google/common/cache/TestingCacheLoaders.java @@ -15,15 +15,16 @@ 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 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. @@ -31,6 +32,7 @@ * @author mike nonemacher */ @GwtCompatible(emulated = true) +@NullUnmarked class TestingCacheLoaders { /** @@ -57,7 +59,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); } @@ -144,7 +146,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() { 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..4aab99e1aaec 100644 --- a/android/guava-tests/test/com/google/common/cache/TestingRemovalListeners.java +++ b/android/guava-tests/test/com/google/common/cache/TestingRemovalListeners.java @@ -18,6 +18,7 @@ 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. @@ -25,6 +26,7 @@ * @author mike nonemacher */ @GwtCompatible(emulated = true) +@NullUnmarked class TestingRemovalListeners { /** Returns a new no-op {@code RemovalListener}. */ 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..db508aae5749 100644 --- a/android/guava-tests/test/com/google/common/cache/TestingWeighers.java +++ b/android/guava-tests/test/com/google/common/cache/TestingWeighers.java @@ -14,11 +14,14 @@ package com.google.common.cache; +import org.jspecify.annotations.NullUnmarked; + /** * Utility {@link Weigher} implementations intended for use in testing. * * @author Charles Fry */ +@NullUnmarked public class TestingWeighers { /** Returns a {@link Weigher} that returns the given {@code constant} for every request. */ 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..5a4934ebcc84 --- /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 static com.google.common.collect.Sets.newHashSet; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Joiner; +import com.google.common.collect.testing.MapInterfaceTest; +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(newHashSet(map.entrySet()), map.entrySet()); + assertEquals(newHashSet(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..29c57ec3cc25 --- /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 static com.google.common.collect.Sets.newHashSet; + +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.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(newHashSet(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..1ded6f1ee474 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. @@ -41,6 +50,7 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@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..30110ad5719e --- /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 static com.google.common.collect.Sets.newHashSet; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Joiner; +import com.google.common.collect.testing.SortedMapInterfaceTest; +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(newHashSet(map.entrySet()), map.entrySet()); + assertEquals(newHashSet(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..8c706a62e346 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}. @@ -31,6 +39,7 @@ */ @SuppressWarnings("serial") // No serialization is used in this test @GwtCompatible(emulated = true) +@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,31 +173,18 @@ 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() { @@ -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..7e58e81b4303 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( + final K key, final 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..e529b1f33660 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/AbstractMapsTransformValuesTest.java @@ -0,0 +1,271 @@ +/* + * 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 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.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(Maps.newHashMap(), 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 = Maps.newHashMap(); + underlying.put("a", 1); + Map map = 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 = + 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 = Maps.newHashMap(); + 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 = 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 = 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 = Maps.newHashMap(); + 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..682f16d54d5a 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> { @@ -64,13 +68,12 @@ protected Collection getValueNotInPopulatedMap() throws UnsupportedOper @Override public void testRemove() { final Map> map; - final String keyToRemove; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { return; } - keyToRemove = map.keySet().iterator().next(); + final String keyToRemove = map.keySet().iterator().next(); if (supportsRemove) { int initialSize = map.size(); map.get(keyToRemove); @@ -80,11 +83,7 @@ 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); } 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..90b8a4792aad 100644 --- a/android/guava-tests/test/com/google/common/collect/AbstractRangeSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/AbstractRangeSetTest.java @@ -19,6 +19,7 @@ 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 +27,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()); 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..3d0b4776be97 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) +@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) { 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(); - } - }; + public EmptyAbstractSequentialIterator() { + super(null); + } + + @Override + protected T computeNext(T previous) { + throw new AssertionFailedError(); + } } - private static class MyException extends RuntimeException {} + private static class BrokenAbstractSequentialIterator extends AbstractSequentialIterator { + + public 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..a613102dac08 100644 --- a/android/guava-tests/test/com/google/common/collect/AbstractTableReadTest.java +++ b/android/guava-tests/test/com/google/common/collect/AbstractTableReadTest.java @@ -16,14 +16,18 @@ 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.base.Objects; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Test cases for {@link Table} read operations. @@ -31,8 +35,9 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) -public abstract class AbstractTableReadTest extends TestCase { - protected Table table; +@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()); @@ -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) @@ -157,11 +161,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 +172,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 +180,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..c0b267b0bc7d 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,13 @@ 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 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 +32,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 +59,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')); + assertNull(table.put("foo", 1, cellValue('a'))); + assertNull(table.put("bar", 1, cellValue('b'))); + assertNull(table.put("foo", 3, cellValue('c'))); + 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)); 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 +94,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)); + assertEquals((Character) 'b', table.put("bar", 1, nullableCellValue(null))); assertNull(table.get("bar", 1)); } 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)); @@ -146,11 +132,7 @@ public void testRemove() { assertNull(table.remove(null, null)); 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 +140,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/ArrayListMultimapTest.java b/android/guava-tests/test/com/google/common/collect/ArrayListMultimapTest.java index 08e5ae8ccf2c..37f513ae3b29 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,6 +35,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Unit tests for {@code ArrayListMultimap}. @@ -40,9 +43,11 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class ArrayListMultimapTest extends TestCase { @GwtIncompatible // suite + @J2ktIncompatible public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -116,11 +121,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 +144,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..a698b357e560 100644 --- a/android/guava-tests/test/com/google/common/collect/ArrayTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/ArrayTableTest.java @@ -16,11 +16,14 @@ 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.annotations.J2ktIncompatible; import com.google.common.base.Objects; import com.google.common.collect.Table.Cell; import com.google.common.testing.EqualsTester; @@ -28,6 +31,8 @@ import com.google.common.testing.SerializableTester; import java.util.Arrays; import java.util.Map; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Test cases for {@link ArrayTable}. @@ -35,10 +40,11 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) -public class ArrayTableTest extends AbstractTableTest { +@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 +131,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() @@ -159,7 +165,7 @@ public void testHashCode() { @Override public void testRow() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); - Map expected = Maps.newHashMap(); + Map expected = Maps.newHashMap(); expected.put(1, 'a'); expected.put(3, 'c'); expected.put(2, null); @@ -169,7 +175,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 = Maps.newHashMap(); expected.put("foo", 'a'); expected.put("bar", 'b'); expected.put("cat", null); @@ -184,35 +190,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 +222,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 +233,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,7 +249,7 @@ 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)); @@ -278,7 +272,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 +284,7 @@ public void testSerialization() { SerializableTester.reserializeAndAssert(table); } + @J2ktIncompatible @GwtIncompatible // reflection public void testNullPointerStatic() { new NullPointerTester().testAllPublicStaticMethods(ArrayTable.class); @@ -357,26 +352,10 @@ public void testAt() { 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) { - } + 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() { @@ -388,26 +367,10 @@ public void testSet() { 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) { - } + 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')); } @@ -423,18 +386,11 @@ public void testEraseAll() { 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')); } @@ -470,60 +426,48 @@ 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()); } + @J2ktIncompatible @GwtIncompatible // serialize 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..5304acbb04da --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/Base.java @@ -0,0 +1,56 @@ +/* + * 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.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); + } + + 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..45015d01e6ab 100644 --- a/android/guava-tests/test/com/google/common/collect/BenchmarkHelpers.java +++ b/android/guava-tests/test/com/google/common/collect/BenchmarkHelpers.java @@ -17,10 +17,11 @@ 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.HashSet; import java.util.LinkedHashSet; import java.util.Map; @@ -32,12 +33,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 +83,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 { @@ -386,7 +389,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 +399,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; } 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..16c98c5cae61 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/CollectSpliteratorsTest.java @@ -0,0 +1,109 @@ +/* + * 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.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 = Lists.newArrayList(); + 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..6c4a951623ea 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,19 @@ 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.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; @@ -75,7 +77,7 @@ private Element[] createQueries(Set elementsInSet, int numQueries) { queryList.addAll(elementsInSet); } List tmp = Lists.newArrayList(elementsInSet); - Collections.shuffle(tmp, random); + shuffle(tmp, random); queryList.addAll(tmp.subList(0, extras)); } @@ -86,7 +88,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 +113,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 +124,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 +139,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..221ba566f7ae 100644 --- a/android/guava-tests/test/com/google/common/collect/Collections2Test.java +++ b/android/guava-tests/test/com/google/common/collect/Collections2Test.java @@ -16,16 +16,16 @@ 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; @@ -40,6 +40,8 @@ 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}. @@ -48,7 +50,9 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class Collections2Test extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(Collections2Test.class.getSimpleName()); @@ -62,30 +66,12 @@ 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 -> !"yyy".equals(input) && !"zzz".equals(input); + static final Predicate LENGTH_1 = input -> input.length() == 1; + + @J2ktIncompatible @GwtIncompatible // suite private static Test testsForFilter() { return CollectionTestSuiteBuilder.using( @@ -109,6 +95,7 @@ public Collection create(String[] elements) { .createTestSuite(); } + @J2ktIncompatible @GwtIncompatible // suite private static Test testsForFilterAll() { return CollectionTestSuiteBuilder.using( @@ -130,6 +117,7 @@ public Collection create(String[] elements) { .createTestSuite(); } + @J2ktIncompatible @GwtIncompatible // suite private static Test testsForFilterLinkedList() { return CollectionTestSuiteBuilder.using( @@ -153,6 +141,7 @@ public Collection create(String[] elements) { .createTestSuite(); } + @J2ktIncompatible @GwtIncompatible // suite private static Test testsForFilterNoNulls() { return CollectionTestSuiteBuilder.using( @@ -176,6 +165,7 @@ public Collection create(String[] elements) { .createTestSuite(); } + @J2ktIncompatible @GwtIncompatible // suite private static Test testsForFilterFiltered() { return CollectionTestSuiteBuilder.using( @@ -200,25 +190,19 @@ 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 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 = newArrayList(); 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 +214,7 @@ public Collection create(String[] elements) { .createTestSuite(); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); @@ -498,7 +483,7 @@ private void assertPermutationsCount(int expected, Collection> permu } public void testToStringImplWithNullEntries() throws Exception { - List list = Lists.newArrayList(); + List<@Nullable String> list = Lists.newArrayList(); 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..c6b3d0c39eb7 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,12 +30,14 @@ 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 { public static Test suite() { TestSuite suite = new TestSuite(); @@ -111,7 +114,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..1fe94825fecc 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,6 +40,7 @@ * @author Dimitris Andreou */ @GwtIncompatible // java.util.Arrays#copyOf(Object[], int), java.lang.reflect.Array +@NullUnmarked public class CompactHashSetTest extends TestCase { public static Test suite() { List> allFeatures = @@ -58,7 +61,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 +72,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 +107,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..1b6c58c61410 100644 --- a/android/guava-tests/test/com/google/common/collect/CompactLinkedHashMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/CompactLinkedHashMapTest.java @@ -14,7 +14,9 @@ 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; @@ -27,12 +29,14 @@ 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 { public static Test suite() { TestSuite suite = new TestSuite(); @@ -136,7 +140,7 @@ private void testHasMapEntriesInOrder(Map map, Object... alternatingKeysAn 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 +174,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..6996938a09f8 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,6 +40,7 @@ * @author Dimitris Andreou */ @GwtIncompatible // java.util.Arrays#copyOf(Object[], int), java.lang.reflect.Array +@NullUnmarked public class CompactLinkedHashSetTest extends TestCase { public static Test suite() { List> allFeatures = @@ -58,7 +62,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 +89,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..be94a9d650b4 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(Collections.emptyList(), 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(Collections.emptyList(), 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..1f3e422b6077 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") 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..aa7836dfe39f 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,10 @@ 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 com.google.common.base.Function; import com.google.common.primitives.Ints; import java.util.List; @@ -30,6 +34,7 @@ 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; @@ -67,7 +73,7 @@ private void testAddAndRemove(ConcurrentMap map) ExecutorService pool = Executors.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 @@ -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..d211a0bed2cd 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,6 +48,7 @@ * @author Cliff L. Biffle * @author mike nonemacher */ +@NullUnmarked public class ConcurrentHashMultisetTest extends TestCase { public static Test suite() { @@ -159,11 +162,7 @@ public void testAdd_laterFewWithOverflow() { 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)); } /** @@ -246,11 +245,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..bf53d89a0dd5 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 */ +/** + * @author Gregory Kick + */ @GwtCompatible(emulated = true) +@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() { @@ -388,6 +388,7 @@ public void testAsList() { assertEquals(ImmutableList.of(1, 2, 3), ImmutableList.copyOf(list.toArray(new Integer[0]))); } + @J2ktIncompatible @GwtIncompatible // suite public static class BuiltTests extends TestCase { public static Test suite() { 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..9d3c0a2a0a71 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/Derived.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** Simple derived class to verify that we handle generics correctly. */ +@GwtCompatible +@NullUnmarked +class Derived extends Base { + public Derived(String s) { + super(s); + } + + 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..163688c867bc 100644 --- a/android/guava-tests/test/com/google/common/collect/EmptyImmutableTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/EmptyImmutableTableTest.java @@ -19,6 +19,7 @@ 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} @@ -26,6 +27,7 @@ * @author Gregory Kick */ @GwtCompatible(emulated = true) +@NullMarked public class EmptyImmutableTableTest extends AbstractImmutableTableTest { private static final ImmutableTable INSTANCE = ImmutableTable.of(); 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..4f5cea264617 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 */ +@J2ktIncompatible // EnumBimap @GwtCompatible(emulated = true) +@NullMarked public class EnumBiMapTest extends TestCase { private enum Currency { DOLLAR, @@ -80,17 +85,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,6 +114,7 @@ public Currency[] createValueArray(int length) { } } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -148,16 +154,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); @@ -183,11 +185,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 +289,14 @@ public void testEntrySet() { assertEquals(3, uniqueEntries.size()); } + @J2ktIncompatible @GwtIncompatible // serialization 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..b0e0bab7e214 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,12 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; +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.SampleElements; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; @@ -34,13 +38,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 */ +@J2ktIncompatible // EnumHashBiMap @GwtCompatible(emulated = true) +@NullUnmarked public class EnumHashBiMapTest extends TestCase { private enum Currency { DOLLAR, @@ -73,17 +80,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,6 +109,7 @@ public String[] createValueArray(int length) { } } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -142,11 +150,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); @@ -197,6 +203,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 +223,13 @@ public void testEntrySet() { assertEquals(3, uniqueEntries.size()); } + @J2ktIncompatible @GwtIncompatible // serialize 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..3d66d0f58836 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,12 +31,14 @@ 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}. @@ -42,8 +46,11 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@J2ktIncompatible // EnumMultiset +@NullUnmarked public class EnumMultisetTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -105,11 +112,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 +121,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 +154,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 +170,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..1d8cdd383907 100644 --- a/android/guava-tests/test/com/google/common/collect/EvictingQueueTest.java +++ b/android/guava-tests/test/com/google/common/collect/EvictingQueueTest.java @@ -16,14 +16,18 @@ 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.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}. @@ -31,14 +35,11 @@ * @author Kurt Alfred Kluever */ @GwtCompatible(emulated = true) +@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 +55,11 @@ public void testCreateWithZeroSize() throws Exception { assertFalse(queue.remove("hi")); assertEquals(0, queue.size()); - try { - queue.element(); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> queue.element()); assertNull(queue.peek()); assertNull(queue.poll()); - try { - queue.remove(); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> queue.remove()); } public void testRemainingCapacity_maxSize0() { @@ -187,6 +180,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..98cd431c904d 100644 --- a/android/guava-tests/test/com/google/common/collect/FauxveridesTest.java +++ b/android/guava-tests/test/com/google/common/collect/FauxveridesTest.java @@ -21,6 +21,8 @@ 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; @@ -28,12 +30,13 @@ import java.lang.reflect.Method; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; -import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; 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,19 @@ 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() { 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() { 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) { @@ -171,12 +163,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 +211,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); @@ -247,11 +239,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; /* diff --git a/android/guava-tests/test/com/google/common/collect/FilteredBiMapTest.java b/android/guava-tests/test/com/google/common/collect/FilteredBiMapTest.java new file mode 100644 index 000000000000..78d4e958ccc3 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/FilteredBiMapTest.java @@ -0,0 +1,29 @@ +/* + * 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.NullMarked; + +@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..a965aa31f010 100644 --- a/android/guava-tests/test/com/google/common/collect/FilteredCollectionsTest.java +++ b/android/guava-tests/test/com/google/common/collect/FilteredCollectionsTestUtil.java @@ -16,7 +16,10 @@ package com.google.common.collect; +import static com.google.common.base.Predicates.not; +import static com.google.common.collect.Sets.newHashSet; 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; @@ -28,15 +31,21 @@ 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 @@ -152,11 +161,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 +173,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 +188,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 +200,7 @@ public abstract static class AbstractFilteredSetTest> extends AbstractFilteredCollectionTest { public void testEqualsAndHashCode() { for (List contents : SAMPLE_INPUTS) { - Set expected = Sets.newHashSet(); + Set expected = newHashSet(); for (Integer i : contents) { if (EVEN.apply(i)) { expected.add(i); @@ -374,79 +371,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/android/guava-tests/test/com/google/common/collect/FilteredMapTest.java b/android/guava-tests/test/com/google/common/collect/FilteredMapTest.java new file mode 100644 index 000000000000..439099b11fda --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/FilteredMapTest.java @@ -0,0 +1,30 @@ +/* + * 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.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class FilteredMapTest extends AbstractFilteredMapTest { + @Override + Map createUnfiltered() { + return Maps.newHashMap(); + } +} 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..01010c52eacf 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,15 @@ 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 junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link Multimaps} filtering methods. @@ -28,6 +32,7 @@ * @author Jared Levy */ @GwtIncompatible // nottested +@NullUnmarked public class FilteredMultimapTest extends TestCase { private static final Predicate> ENTRY_PREDICATE = @@ -57,7 +62,7 @@ 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)); } @@ -74,7 +79,7 @@ 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 +90,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..049dae5b2760 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; @@ -40,7 +49,8 @@ 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}. @@ -48,6 +58,7 @@ * @author Marcin Mikosik */ @GwtCompatible(emulated = true) +@NullUnmarked public class FluentIterableTest extends TestCase { @GwtIncompatible // NullPointerTester @@ -57,17 +68,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 +82,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 +111,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 +132,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 +141,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 +224,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 +244,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 +268,7 @@ public void testOfToString() { } public void testToString() { - assertEquals("[]", FluentIterable.from(Collections.emptyList()).toString()); + assertEquals("[]", FluentIterable.from(emptyList()).toString()); assertEquals("[]", FluentIterable.of().toString()); assertEquals( @@ -328,12 +332,12 @@ public void testAppend_emptyList() { } 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 +350,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); @@ -373,7 +377,7 @@ public void testFilterByType() throws Exception { public void testAnyMatch() { ArrayList list = Lists.newArrayList(); FluentIterable iterable = FluentIterable.from(list); - Predicate predicate = Predicates.equalTo("pants"); + Predicate predicate = equalTo("pants"); assertFalse(iterable.anyMatch(predicate)); list.add("cool"); @@ -385,7 +389,7 @@ public void testAnyMatch() { public void testAllMatch() { List list = Lists.newArrayList(); FluentIterable iterable = FluentIterable.from(list); - Predicate predicate = Predicates.equalTo("cool"); + Predicate predicate = equalTo("cool"); assertTrue(iterable.allMatch(predicate)); list.add("cool"); @@ -396,8 +400,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 +429,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 +484,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 +508,7 @@ public void testFirst_iterable() { } public void testFirst_emptyIterable() { - Set set = Sets.newHashSet(); + Set set = newHashSet(); assertThat(FluentIterable.from(set).first()).isAbsent(); } @@ -523,15 +519,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 +543,7 @@ public void testLast_iterable() { } public void testLast_emptyIterable() { - Set set = Sets.newHashSet(); + Set set = newHashSet(); assertThat(FluentIterable.from(set).last()).isAbsent(); } @@ -571,12 +563,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() { @@ -663,11 +655,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 +669,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 +736,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 +769,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,60 +801,57 @@ 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() { + public void testCopyInto_nonCollection() { final ArrayList list = Lists.newArrayList(1, 2, 3); final ArrayList iterList = Lists.newArrayList(9, 8, 7); @@ -905,17 +883,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) { 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..b09fc124a98d 100644 --- a/android/guava-tests/test/com/google/common/collect/ForMapMultimapAsMapImplementsMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForMapMultimapAsMapImplementsMapTest.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 a {@link Multimaps#forMap} multimap with {@link @@ -28,6 +29,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public class ForMapMultimapAsMapImplementsMapTest extends AbstractMultimapAsMapImplementsMapTest { public ForMapMultimapAsMapImplementsMapTest() { 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..f8b9cb02f4f8 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingCollectionTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingCollectionTest.java @@ -29,6 +29,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link ForwardingCollection}. @@ -37,6 +38,7 @@ * @author Hayward Chan * @author Louis Wasserman */ +@NullUnmarked public class ForwardingCollectionTest extends TestCase { static final class StandardImplForwardingCollection extends ForwardingCollection { private final Collection backingCollection; 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..3bd70757f696 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingConcurrentMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingConcurrentMapTest.java @@ -19,12 +19,14 @@ 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 { 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..6811b1c2393a 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,10 +35,10 @@ 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); } }); } 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..e369de4254cd 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,10 +35,10 @@ 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); } }); } 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..ba974e0dc9bd 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); } }); } 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..ef5915545696 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); } 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..908c9c9dde42 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; @@ -44,9 +43,14 @@ 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 +58,7 @@ * @author Hayward Chan * @author Louis Wasserman */ +@NullUnmarked public class ForwardingMapTest extends TestCase { static class StandardImplForwardingMap extends ForwardingMap { private final Map backingMap; @@ -83,12 +88,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); } @@ -170,7 +175,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") @@ -222,7 +227,7 @@ public Set> entrySet() { return new StandardEntrySet() { @Override public Iterator> iterator() { - return Iterators.emptyIterator(); + return emptyIterator(); } }; } @@ -329,38 +334,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(final 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 +384,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..ec93966ea344 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); } }); } 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..ca4f28aef3ad 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); } @@ -166,7 +170,7 @@ 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") @@ -197,8 +201,7 @@ protected Multiset create(String[] elements) { */ @Override protected Set create(String[] elements) { - final Multiset inner = - LinkedHashMultiset.create(Arrays.asList(elements)); + final Multiset inner = LinkedHashMultiset.create(asList(elements)); return new ForwardingMultiset() { @Override protected Multiset delegate() { @@ -277,7 +280,7 @@ public boolean retainAll(Collection collection) { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { throw new UnsupportedOperationException(); } 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..170f6d5e6f02 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingNavigableMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingNavigableMapTest.java @@ -39,6 +39,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 +48,7 @@ * @author Robert Konigsberg * @author Louis Wasserman */ +@NullUnmarked public class ForwardingNavigableMapTest extends TestCase { static class StandardImplForwardingNavigableMap extends ForwardingNavigableMap { private final NavigableMap backingMap; @@ -75,12 +78,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 +137,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 +187,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,7 +245,7 @@ protected NavigableMap delegate() { } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { return standardLastEntry(); } } 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..6256c97f721f 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,6 @@ 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.Collection; import java.util.Collections; import java.util.List; @@ -34,12 +35,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 +58,7 @@ protected NavigableSet delegate() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { return standardEquals(object); } @@ -119,32 +123,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(); } @@ -169,7 +173,7 @@ public static Test suite() { @Override protected Set create(String[] elements) { return new StandardImplForwardingNavigableSet<>( - new SafeTreeSet(Arrays.asList(elements))); + new SafeTreeSet(asList(elements))); } @Override 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..efcdf0e87d5e 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingObjectTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingObjectTest.java @@ -16,15 +16,19 @@ 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() { @@ -40,7 +44,7 @@ protected Object delegate() { } public void testEqualsSymmetric() { - final Set delegate = Sets.newHashSet("foo"); + final 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..96ad8da83690 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingQueueTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingQueueTest.java @@ -29,6 +29,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 ForwardingQueue}. @@ -36,6 +38,7 @@ * @author Robert Konigsberg * @author Louis Wasserman */ +@NullUnmarked public class ForwardingQueueTest extends TestCase { static final class StandardImplForwardingQueue extends ForwardingQueue { @@ -106,12 +109,12 @@ 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(); } } 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..f25b2aa3ee85 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); } }); } 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..73d6004a6058 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingSetTest.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 ForwardingSet}. @@ -38,6 +40,7 @@ * @author Robert Konigsberg * @author Louis Wasserman */ +@NullUnmarked public class ForwardingSetTest extends TestCase { static class StandardImplForwardingSet extends ForwardingSet { private final Set backingSet; @@ -52,7 +55,7 @@ protected Set delegate() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { return standardEquals(object); } 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..a2f6c8739265 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,7 +53,7 @@ public ForwardingSortedMapImplementsMapTest() { @Override protected SortedMap makeEmptyMap() { return new SimpleForwardingSortedMap<>( - new TreeMap(Ordering.natural().nullsFirst())); + new TreeMap(Ordering.natural().nullsFirst())); } @Override @@ -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..2a6777a0590a 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); } 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..78711789c2fc 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); } @@ -183,7 +186,7 @@ public static Test suite() { @Override protected Multiset create(String[] elements) { return new StandardImplForwardingSortedMultiset<>( - TreeMultiset.create(Arrays.asList(elements))); + TreeMultiset.create(asList(elements))); } @Override 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..237eb0570eac 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); } }); } 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..188a3892a027 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,21 @@ 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.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 +55,7 @@ protected SortedSet delegate() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { return standardEquals(object); } @@ -126,7 +130,7 @@ public static Test suite() { @Override protected SortedSet create(String[] elements) { return new StandardImplForwardingSortedSet<>( - new SafeTreeSet(Arrays.asList(elements))); + new SafeTreeSet(asList(elements))); } @Override 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..79e6ad3d16a9 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); } }); } 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..214513297506 100644 --- a/android/guava-tests/test/com/google/common/collect/GeneralRangeTest.java +++ b/android/guava-tests/test/com/google/common/collect/GeneralRangeTest.java @@ -16,14 +16,19 @@ 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.annotations.J2ktIncompatible; import com.google.common.base.Objects; import com.google.common.testing.NullPointerTester; import java.util.Arrays; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code GeneralRange}. @@ -31,36 +36,35 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@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,7 +82,7 @@ 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)); } @@ -86,7 +90,7 @@ public void testCreateSingletonRangeSucceeds() { } 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/android/guava-tests/test/com/google/common/collect/HashBasedTableRowTest.java b/android/guava-tests/test/com/google/common/collect/HashBasedTableRowTest.java new file mode 100644 index 000000000000..1b6f4a44a97f --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/HashBasedTableRowTest.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.RowTests; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class HashBasedTableRowTest extends RowTests { + public HashBasedTableRowTest() { + super(false, true, true, true, true); + } + + @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..d0d3950d9bc4 100644 --- a/android/guava-tests/test/com/google/common/collect/HashBasedTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/HashBasedTableTest.java @@ -16,12 +16,16 @@ 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}. @@ -29,10 +33,11 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) -public class HashBasedTableTest extends AbstractTableTest { +@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..d000fdc5cfac 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,6 +34,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Tests for {@link HashBiMap}. @@ -39,8 +42,10 @@ * @author Mike Bostock */ @GwtCompatible(emulated = true) +@NullMarked public class HashBiMapTest extends TestCase { + @J2ktIncompatible public static final class HashBiMapGenerator extends TestStringBiMapGenerator { @Override protected BiMap create(Entry[] entries) { @@ -52,6 +57,7 @@ protected BiMap create(Entry[] entries) { } } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -138,9 +144,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 +156,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 +168,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 +180,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 +192,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 +204,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 +214,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 +226,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 +240,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 +251,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..e4decb57c2c8 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,6 +30,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Unit tests for {@link HashMultimap}. @@ -34,8 +38,10 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class HashMultimapTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -99,17 +105,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..421ab58e3713 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}. @@ -39,8 +40,10 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class HashMultisetTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -59,6 +62,7 @@ public static Test suite() { return suite; } + @J2ktIncompatible private static TestStringMultisetGenerator hashMultisetGenerator() { return new TestStringMultisetGenerator() { @Override @@ -85,11 +89,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,6 +104,7 @@ public void testSerializationContainingSelf() { assertSame(copy, copy.iterator().next()); } + @J2ktIncompatible @GwtIncompatible // Only used by @GwtIncompatible code private static class MultisetHolder implements Serializable { public Multiset member; @@ -110,6 +116,7 @@ private static class MultisetHolder implements Serializable { 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/android/guava-tests/test/com/google/common/collect/ImmutableBiMapInverseMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableBiMapInverseMapInterfaceTest.java new file mode 100644 index 000000000000..2bcf2a7e0793 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ImmutableBiMapInverseMapInterfaceTest.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 ImmutableBiMapInverseMapInterfaceTest + extends AbstractImmutableBiMapMapInterfaceTest { + @Override + protected Map makeEmptyMap() { + return ImmutableBiMap.of(); + } + + @Override + protected Map makePopulatedMap() { + return ImmutableBiMap.of(1, "one", 2, "two", 3, "three").inverse(); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "minus one"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return -1; + } +} 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..964be11d9790 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,6 +44,8 @@ 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}. @@ -47,19 +53,16 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class ImmutableBiMapTest extends TestCase { // TODO: Reduce duplication of ImmutableMapTest code + @J2ktIncompatible @GwtIncompatible // suite 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 +95,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 = Maps.newLinkedHashMap(); + 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..2c855c95c7c4 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,19 +30,21 @@ 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.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 { public static Test suite() { TestSuite suite = new TestSuite(); @@ -50,13 +56,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 +87,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()); @@ -108,30 +114,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 +143,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 +158,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 +184,7 @@ public SampleElements> samples() { @Override @SuppressWarnings("unchecked") public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override @@ -213,7 +211,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..b6e583163f02 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableEnumMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableEnumMapTest.java @@ -17,15 +17,16 @@ 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; @@ -34,6 +35,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Tests for {@code ImmutableEnumMap}. @@ -41,7 +43,9 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@NullMarked public class ImmutableEnumMapTest extends TestCase { + @J2ktIncompatible public static class ImmutableEnumMapGenerator extends TestEnumMapGenerator { @Override protected Map create(Entry[] entries) { @@ -53,6 +57,7 @@ protected Map create(Entry[] entries) { } } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -80,7 +85,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 +98,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..f758b8daf3c2 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ImmutableListCopyOfConcurrentlyModifiedInputTest.java @@ -0,0 +1,199 @@ +/* + * 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.collect.Sets.newHashSet; +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.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(final 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( + 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 = + 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..c4f9e661e888 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,6 +42,8 @@ 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}. @@ -44,7 +51,9 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class ImmutableListMultimapTest extends TestCase { + @J2ktIncompatible public static class ImmutableListMultimapGenerator extends TestStringListMultimapGenerator { @Override protected ListMultimap create(Entry[] entries) { @@ -56,6 +65,7 @@ protected ListMultimap create(Entry[] entries) { } } + @J2ktIncompatible public static class ImmutableListMultimapCopyOfEntriesGenerator extends TestStringListMultimapGenerator { @Override @@ -64,6 +74,7 @@ protected ListMultimap create(Entry[] entries) { } } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -81,6 +92,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,20 +138,14 @@ 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() { @@ -133,8 +176,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 +186,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 +205,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 +248,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 +353,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 +363,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 +379,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 +409,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 +560,7 @@ private static void assertMultimapEquals( } } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { Multimap multimap = createMultimap(); @@ -566,9 +574,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..1018f658b60f 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}. @@ -63,8 +63,10 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class ImmutableListTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -112,727 +114,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..1a3ceea2abd3 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,24 @@ 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.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}. @@ -66,8 +71,11 @@ * @author Jesse Wilson */ @GwtCompatible(emulated = true) +@SuppressWarnings("AlwaysThrows") +@NullMarked public class ImmutableMapTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -166,547 +174,641 @@ 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<>(); + 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.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)); @@ -734,6 +836,7 @@ public void testAsMultimapCaches() { assertSame(multimap1, multimap2); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); @@ -745,12 +848,11 @@ 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 { @@ -761,7 +863,7 @@ public IntHolder(int value) { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return (o instanceof IntHolder) && ((IntHolder) o).value == value; } @@ -778,12 +880,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); @@ -795,6 +898,7 @@ public void testViewSerialization() { assertTrue(reserializedValues instanceof ImmutableCollection); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testKeySetIsSerializable_regularImmutableMap() { class NonSerializableClass {} @@ -806,6 +910,7 @@ class NonSerializableClass {} LenientSerializableTester.reserializeAndAssertLenient(set); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testValuesCollectionIsSerializable_regularImmutableMap() { class NonSerializableClass {} @@ -818,10 +923,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 +955,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-tests/test/com/google/common/collect/ImmutableMapWithBadHashesMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableMapWithBadHashesMapInterfaceTest.java new file mode 100644 index 000000000000..d7e4910b2af8 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ImmutableMapWithBadHashesMapInterfaceTest.java @@ -0,0 +1,52 @@ +/* + * 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.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 + protected Map makePopulatedMap() { + Colliders colliders = new Colliders(); + return ImmutableMap.of( + colliders.e0(), 0, + colliders.e1(), 1, + colliders.e2(), 2, + colliders.e3(), 3); + } + + @Override + protected Object getKeyNotInPopulatedMap() { + return new Colliders().e4(); + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return 4; + } +} 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..2e4ea543f74d 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableMultimapTest.java @@ -16,15 +16,24 @@ 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}. @@ -32,32 +41,66 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@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(); @@ -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..0fd2b6a0db10 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,6 +49,8 @@ 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}. @@ -51,8 +58,10 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class ImmutableMultisetTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite // TODO(cpovirk): add to collect/gwt/suites public static Test suite() { TestSuite suite = new TestSuite(); @@ -196,6 +205,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 +215,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 +238,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 +262,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 +286,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 +399,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 +465,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 +473,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 +481,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 +512,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..f6f87d4dd56e 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++) { + final 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..527a3dfe8d6e 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,6 +37,7 @@ * @author Louis Wasserman */ @GwtIncompatible // ImmutableRangeSet +@NullUnmarked public class ImmutableRangeSetTest extends AbstractRangeSetTest { static final class ImmutableRangeSetIntegerAsSetGenerator implements TestSetGenerator { @@ -312,6 +315,7 @@ public void testMultipleBoundedAboveRanges() { assertEquals(expectedComplement, rangeSet.complement()); } + @SuppressWarnings("DoNotCall") public void testAddUnsupported() { RangeSet rangeSet = ImmutableRangeSet.builder() @@ -319,14 +323,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 +334,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 +347,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 +358,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 +411,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..db36f10b9a43 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,6 +41,8 @@ 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}. @@ -44,7 +50,9 @@ * @author Mike Ward */ @GwtCompatible(emulated = true) +@NullMarked public class ImmutableSetMultimapTest extends TestCase { + @J2ktIncompatible private static final class ImmutableSetMultimapGenerator extends TestStringSetMultimapGenerator { @Override protected SetMultimap create(Entry[] entries) { @@ -56,6 +64,7 @@ protected SetMultimap create(Entry[] entries) { } } + @J2ktIncompatible private static final class ImmutableSetMultimapCopyOfEntriesGenerator extends TestStringSetMultimapGenerator { @Override @@ -64,6 +73,7 @@ protected SetMultimap create(Entry[] entries) { } } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -81,6 +91,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,20 +191,14 @@ 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() { @@ -200,55 +296,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 +448,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 +484,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 +610,7 @@ private static void assertMultimapEquals( } } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { Multimap multimap = createMultimap(); @@ -560,12 +624,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 +658,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..8df4e5facca0 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; @@ -35,11 +38,11 @@ import com.google.common.collect.testing.google.SetGenerators.ImmutableSetWithBadHashesGenerator; import com.google.common.testing.EqualsTester; 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}. @@ -49,8 +52,10 @@ * @author Nick Kralevich */ @GwtCompatible(emulated = true) +@NullMarked public class ImmutableSetTest extends AbstractImmutableSetTest { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -168,7 +173,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) { @@ -263,7 +267,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 +283,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 +329,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(); 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..ca9c6a564a6c 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; @@ -42,11 +44,14 @@ 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}. @@ -56,9 +61,12 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@SuppressWarnings("AlwaysThrows") +@NullMarked public class ImmutableSortedMapTest extends TestCase { // TODO: Avoid duplicating code in ImmutableMapTest + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -118,588 +126,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(); + 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 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)); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); @@ -713,16 +623,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,27 +638,24 @@ 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 { @@ -761,7 +666,7 @@ public IntHolder(int value) { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return (o instanceof IntHolder) && ((IntHolder) o).value == value; } @@ -778,12 +683,13 @@ 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); @@ -794,72 +700,62 @@ public void testViewSerialization() { Lists.newArrayList(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 +766,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..fa36cbe79847 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,13 @@ 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.Sets.newHashSet; 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; @@ -39,12 +42,14 @@ 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 { public static Test suite() { TestSuite suite = new TestSuite(); @@ -95,7 +100,7 @@ public List order(List insertionOrder) { new TestStringListGenerator() { @Override protected List create(String[] elements) { - Set set = Sets.newHashSet(); + Set set = newHashSet(); ImmutableSortedMultiset.Builder builder = ImmutableSortedMultiset.naturalOrder(); for (String s : elements) { @@ -175,15 +180,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 +190,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 +213,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 +236,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 +259,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 +383,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..8d45a44e8d5d 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,6 +55,7 @@ import java.util.TreeSet; import junit.framework.Test; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Unit tests for {@link ImmutableSortedSet}. @@ -55,8 +63,10 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class ImmutableSortedSetTest extends AbstractImmutableSetTest { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -201,7 +211,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 +238,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 +291,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 +300,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 +308,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 +323,7 @@ public void testSingle_last() { assertEquals("e", set.last()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSingle_serialization() { SortedSet set = of("e"); @@ -392,8 +396,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 +405,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 +413,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 +436,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 +535,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 +554,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 +564,7 @@ public void testExplicitEmpty_serialization() { assertSame(set.comparator(), copy.comparator()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testExplicit_serialization() { SortedSet set = @@ -572,7 +572,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 +738,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 +747,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 +769,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 +793,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 +814,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 +822,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 +879,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 +898,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 +911,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 +926,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 +942,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 +951,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 +973,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 +985,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 +996,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 +1007,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 +1018,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 +1067,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 +1080,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 +1093,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 +1106,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 +1124,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 +1133,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 +1143,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 +1175,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..b194d2abcd05 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableTableTest.java @@ -16,11 +16,16 @@ 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} @@ -28,14 +33,15 @@ * @author Gregory Kick */ @GwtCompatible(emulated = true) -public class ImmutableTableTest extends AbstractTableReadTest { +@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,39 +74,27 @@ 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() { @@ -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..938932850c83 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,35 @@ 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.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.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; @@ -47,6 +65,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}. @@ -55,15 +75,16 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@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 +112,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) { + final 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 +143,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,62 +163,50 @@ 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(); + assertNull(Iterables.<@Nullable String>getOnlyElement(iterable, null)); } 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)); } @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)); } @@ -212,55 +221,51 @@ public void testToArray() { public void testAny() { List list = newArrayList(); - Predicate predicate = Predicates.equalTo("pants"); + 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"); + 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")); + assertNull(find(list, Predicates.alwaysFalse(), null)); + 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 +279,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 +303,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 +317,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 +365,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 +386,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 +395,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 +443,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,8 +453,8 @@ 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> partitions = Iterables.partition(source, 2); @@ -476,9 +467,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() { @@ -513,6 +504,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 +519,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 +560,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 +604,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) @@ -715,11 +691,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) { @@ -776,12 +748,8 @@ public void testGet_emptyIterable() { } 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 +779,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(); + assertNull(Iterables.<@Nullable String>getFirst(iterable, null)); } public void testGetFirst_withDefault_multiple() { @@ -836,12 +804,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 +814,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(); + assertNull(Iterables.<@Nullable String>getLast(iterable, null)); } public void testGetLast_withDefault_multiple() { @@ -874,7 +838,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 +857,7 @@ 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)); } public void testGetLast_iterable() { @@ -904,12 +866,8 @@ public void testGetLast_iterable() { } public void testGetLast_emptyIterable() { - Set set = Sets.newHashSet(); - try { - Iterables.getLast(set); - fail(); - } catch (NoSuchElementException e) { - } + Set set = newHashSet(); + assertThrows(NoSuchElementException.class, () -> Iterables.getLast(set)); } public void testUnmodifiableIterable() { @@ -917,11 +875,7 @@ 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()); } @@ -938,32 +892,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() { @@ -1015,7 +969,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 +979,7 @@ public boolean apply(String s) { })); assertEquals(newArrayList("a", "c", "e"), list); assertFalse( - Iterables.removeIf( + removeIf( list, new Predicate() { @Override @@ -1043,7 +997,7 @@ public void testRemoveIf_randomAccess_notPermittingDuplicates() { assertTrue(uniqueList instanceof RandomAccess); assertTrue( - Iterables.removeIf( + removeIf( uniqueList, new Predicate() { @Override @@ -1053,7 +1007,7 @@ public boolean apply(String s) { })); assertEquals(newArrayList("a", "c", "e"), uniqueList); assertFalse( - Iterables.removeIf( + removeIf( uniqueList, new Predicate() { @Override @@ -1076,7 +1030,7 @@ public Integer apply(String s) { } }); assertTrue( - Iterables.removeIf( + removeIf( transformed, new Predicate() { @Override @@ -1086,7 +1040,7 @@ public boolean apply(Integer n) { })); assertEquals(newArrayList("1", "3", "5"), list); assertFalse( - Iterables.removeIf( + removeIf( transformed, new Predicate() { @Override @@ -1100,7 +1054,7 @@ public boolean apply(Integer n) { public void testRemoveIf_noRandomAccess() { List list = Lists.newLinkedList(asList("a", "b", "c", "d", "e")); assertTrue( - Iterables.removeIf( + removeIf( list, new Predicate() { @Override @@ -1110,7 +1064,7 @@ public boolean apply(String s) { })); assertEquals(newArrayList("a", "c", "e"), list); assertFalse( - Iterables.removeIf( + removeIf( list, new Predicate() { @Override @@ -1131,7 +1085,7 @@ public Iterator iterator() { } }; assertTrue( - Iterables.removeIf( + removeIf( iterable, new Predicate() { @Override @@ -1141,7 +1095,7 @@ public boolean apply(String s) { })); assertEquals(newArrayList("a", "c", "e"), list); assertFalse( - Iterables.removeIf( + removeIf( iterable, new Predicate() { @Override @@ -1157,30 +1111,6 @@ 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")); @@ -1215,12 +1145,7 @@ public void testConsumingIterable_duelingIterators() { 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() { @@ -1277,28 +1202,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 = @@ -1330,17 +1255,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() { @@ -1372,7 +1292,7 @@ public void testMergeSorted_pyramid() { list.add(j); allIntegers.add(j); } - iterables.add(Ordering.natural().sortedCopy(list)); + iterables.add(Ordering.natural().sortedCopy(list)); } verifyMergeSorted(iterables, allIntegers); @@ -1389,12 +1309,13 @@ public void testMergeSorted_skipping_pyramid() { 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,9 +1326,9 @@ 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)); } 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..83d53f7b8f68 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,35 @@ 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.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,6 +55,7 @@ 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; @@ -57,6 +74,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 {@code Iterators}. @@ -64,8 +83,10 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@NullMarked public class IteratorsTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(IteratorsTest.class.getSimpleName()); @@ -74,76 +95,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 +142,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 +152,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,76 +162,60 @@ 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(); + assertNull(Iterators.<@Nullable String>getOnlyElement(iterator, null)); } 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) @@ -256,7 +227,7 @@ public void testToArrayEmpty() { @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)); } @@ -271,23 +242,23 @@ public void testToArray() { 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 +267,7 @@ public void testFilterMatchAll() { public void testFilterNothing() { Iterator unfiltered = Collections.emptyList().iterator(); Iterator filtered = - Iterators.filter( + filter( unfiltered, new Predicate() { @Override @@ -305,7 +276,7 @@ public boolean apply(String s) { } }); - List expected = Collections.emptyList(); + List expected = emptyList(); List actual = Lists.newArrayList(filtered); assertEquals(expected, actual); } @@ -324,128 +295,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"); + 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"); + 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)); + assertNull(find(iterator, Predicates.alwaysFalse(), null)); 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 +453,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,11 +465,7 @@ public Integer apply(String from) { }); result.next(); - try { - result.next(); - fail("Expected NFE"); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> result.next()); } public void testNullFriendlyTransform() { @@ -510,9 +473,9 @@ public void testNullFriendlyTransform() { 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 +505,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 +529,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 +566,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 +623,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 +640,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 +660,6 @@ protected Iterator newTargetIterator() { @GwtIncompatible // slow (~5s) public void testConcatNoIteratorsYieldsEmpty() { new EmptyIteratorTester() { - @SuppressWarnings("unchecked") @Override protected Iterator newTargetIterator() { return Iterators.concat(); @@ -724,7 +670,6 @@ protected Iterator newTargetIterator() { @GwtIncompatible // slow (~5s) public void testConcatOneEmptyIteratorYieldsEmpty() { new EmptyIteratorTester() { - @SuppressWarnings("unchecked") @Override protected Iterator newTargetIterator() { return Iterators.concat(iterateOver()); @@ -745,7 +690,6 @@ protected Iterator newTargetIterator() { @GwtIncompatible // slow (~3s) public void testConcatSingletonYieldsSingleton() { new SingletonIteratorTester() { - @SuppressWarnings("unchecked") @Override protected Iterator newTargetIterator() { return Iterators.concat(iterateOver(1)); @@ -796,52 +740,43 @@ 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() { @@ -889,6 +824,7 @@ public void testAddAllToSet() { assertFalse(changed); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); @@ -916,8 +852,9 @@ protected DoubletonIteratorTester() { } } - 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() { @@ -926,65 +863,61 @@ public void testElementsEqual() { // Base case. a = Lists.newArrayList(); - b = Collections.emptySet(); - assertTrue(Iterators.elementsEqual(a.iterator(), b.iterator())); + 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 +926,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 +963,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 +973,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 +993,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 +1041,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 +1086,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 +1107,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 +1126,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 +1141,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 +1157,8 @@ public void testAsEnumerationTypical() { assertFalse(enumer.hasMoreElements()); } - private static Enumeration enumerate(Integer... ints) { - Vector vector = new Vector<>(asList(ints)); + private static Enumeration enumerate(int... ints) { + Vector vector = new Vector<>(Ints.asList(ints)); return vector.elements(); } @@ -1289,7 +1168,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 +1178,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) { - } + assertThrows(IllegalArgumentException.class, () -> Iterators.limit(list.iterator(), -1)); assertFalse(Iterators.limit(list.iterator(), 0).hasNext()); assertFalse(Iterators.limit(list.iterator(), 1).hasNext()); @@ -1346,18 +1223,18 @@ protected Iterator newTargetIterator() { } 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(); + assertNull(Iterators.<@Nullable String>getNext(iterator, null)); } public void testGetNext_withDefault_two() { @@ -1374,26 +1251,22 @@ public void testGetLast_basic() { public void testGetLast_exception() { List list = newArrayList(); - try { - getLast(list.iterator()); - fail(); - } catch (NoSuchElementException expected) { - } + 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(); + assertNull(Iterators.<@Nullable String>getLast(iterator, null)); } public void testGetLast_withDefault_two() { @@ -1415,11 +1288,7 @@ public void testGet_atSize() { 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()); } @@ -1428,33 +1297,21 @@ public void testGet_pastEnd() { 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(); 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() { @@ -1489,12 +1346,7 @@ public void testGet_withDefault_negativeIndex() { 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()); } @@ -1519,20 +1371,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 +1389,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,6 +1434,7 @@ public void testRetainAll() { assertEquals(newArrayList("b", "d"), list); } + @J2ktIncompatible @GwtIncompatible // ListTestSuiteBuilder private static Test testsForRemoveAllAndRetainAll() { return ListTestSuiteBuilder.using( @@ -1651,24 +1500,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,7 +1520,7 @@ 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()); } 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..00f8517bfe12 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,12 @@ package com.google.common.collect; +import static java.util.Arrays.asList; + import com.google.common.annotations.GwtCompatible; 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 +30,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 +55,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); 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..51378a31b49e 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 @@ -38,6 +40,7 @@ * parameter for non-GWT, non-test files, and it didn't seem worth adding one for this unusual case. */ @GwtCompatible(emulated = true) +@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..6b758aa13d1d 100644 --- a/android/guava-tests/test/com/google/common/collect/LinkedHashMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/LinkedHashMultimapTest.java @@ -17,6 +17,9 @@ 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; @@ -26,6 +29,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,7 +38,6 @@ 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.Collection; import java.util.Iterator; import java.util.List; @@ -43,6 +46,8 @@ 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}. @@ -50,8 +55,10 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class LinkedHashMultimapTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -114,8 +121,8 @@ public void testToString() { Multimap 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()); } @@ -130,11 +137,13 @@ public void testOrderingUnmodifiable() { assertOrderingReadOnly(Multimaps.unmodifiableMultimap(multimap)); } + @J2ktIncompatible // Synchronized public void testOrderingSynchronized() { Multimap multimap = initializeMultimap5(); - assertOrderingReadOnly(Multimaps.synchronizedMultimap(multimap)); + assertOrderingReadOnly(synchronizedMultimap(multimap)); } + @J2ktIncompatible @GwtIncompatible // SeriazableTester public void testSerializationOrdering() { Multimap multimap = initializeMultimap5(); @@ -142,6 +151,7 @@ public void testSerializationOrdering() { assertOrderingReadOnly(copy); } + @J2ktIncompatible @GwtIncompatible // SeriazableTester public void testSerializationOrderingKeysAndEntries() { Multimap multimap = LinkedHashMultimap.create(); @@ -167,11 +177,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(); @@ -202,7 +212,7 @@ public void testOrderingUpdates() { } public void testToStringNullExact() { - Multimap multimap = LinkedHashMultimap.create(); + Multimap<@Nullable String, @Nullable Integer> multimap = LinkedHashMultimap.create(); multimap.put("foo", 3); multimap.put("foo", -1); @@ -262,17 +272,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 @@ -282,7 +284,7 @@ public void testGetIteration() { MODIFIABLE, newLinkedHashSet(asList(2, 3, 4, 7, 8)), IteratorTester.KnownOrder.KNOWN_ORDER) { - private Multimap multimap; + private @Nullable Multimap multimap; @Override protected Iterator newTargetIterator() { @@ -302,19 +304,18 @@ protected void verify(List elements) { @GwtIncompatible // unreasonably slow public void testEntriesIteration() { - @SuppressWarnings("unchecked") Set> set = Sets.newLinkedHashSet( 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 Multimap multimap; @Override protected Iterator> newTargetIterator() { @@ -339,7 +340,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() { @@ -361,7 +362,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 Multimap multimap; @Override protected Iterator newTargetIterator() { @@ -386,7 +387,7 @@ public void testKeySetIteration() { MODIFIABLE, newLinkedHashSet(asList("foo", "bar", "baz", "dog", "cat")), IteratorTester.KnownOrder.KNOWN_ORDER) { - private Multimap multimap; + private @Nullable Multimap multimap; @Override protected Iterator newTargetIterator() { @@ -410,18 +411,17 @@ protected void verify(List elements) { @GwtIncompatible // unreasonably slow public void testAsSetIteration() { - @SuppressWarnings("unchecked") Set>> set = newLinkedHashSet( 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 Multimap multimap; @Override protected Iterator>> newTargetIterator() { 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..34d888b72361 100644 --- a/android/guava-tests/test/com/google/common/collect/LinkedHashMultisetTest.java +++ b/android/guava-tests/test/com/google/common/collect/LinkedHashMultisetTest.java @@ -21,16 +21,17 @@ 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.List; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Unit test for {@link LinkedHashMultiset}. @@ -38,8 +39,10 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@NullMarked public class LinkedHashMultisetTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -59,6 +62,7 @@ public static Test suite() { return suite; } + @J2ktIncompatible private static TestStringMultisetGenerator linkedHashMultisetGenerator() { return new TestStringMultisetGenerator() { @Override @@ -101,7 +105,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..9d6248478d86 100644 --- a/android/guava-tests/test/com/google/common/collect/LinkedListMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/LinkedListMultimapTest.java @@ -17,6 +17,8 @@ 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.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.IteratorFeature.MODIFIABLE; @@ -24,9 +26,11 @@ 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,9 +39,9 @@ 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.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.ListIterator; @@ -47,6 +51,8 @@ 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}. @@ -54,8 +60,10 @@ * @author Mike Bostock */ @GwtCompatible(emulated = true) +@NullMarked public class LinkedListMultimapTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -119,8 +127,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 +150,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 +228,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 +295,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 +332,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}) { 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 +374,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() { @@ -405,13 +403,13 @@ public void testValuesIteration() { 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); @@ -432,7 +430,7 @@ public void testKeySetIteration() { MODIFIABLE, newLinkedHashSet(asList("foo", "bar", "baz", "dog", "cat")), IteratorTester.KnownOrder.KNOWN_ORDER) { - private Multimap multimap; + private @Nullable Multimap multimap; @Override protected Iterator newTargetIterator() { @@ -454,21 +452,20 @@ protected void verify(List elements) { }.test(); } - @SuppressWarnings("unchecked") @GwtIncompatible // unreasonably slow public void testAsSetIteration() { Set>> set = Sets.newLinkedHashSet( 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() { @@ -496,4 +493,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..9e6d1a9506fa 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) +@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,9 +243,8 @@ 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 { @@ -270,9 +280,8 @@ protected ArraysAsListExample(String name) { @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); } } @@ -288,6 +297,7 @@ public List createList(Class listType, Collection content } } + @J2ktIncompatible @GwtIncompatible // CopyOnWriteArrayList private static final class CopyOnWriteListExample extends ListExample { 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..f881b2c8c65b 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}. @@ -59,6 +71,7 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class ListsTest extends TestCase { private static final Collection SOME_COLLECTION = asList(0, 1, 1); @@ -98,6 +111,7 @@ public String apply(Number n) { private static final long serialVersionUID = 0; } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -109,7 +123,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 +141,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); } }) @@ -149,7 +163,7 @@ protected List create(String[] elements) { 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") @@ -169,7 +183,7 @@ protected List create(String[] elements) { 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 +200,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") @@ -203,7 +217,7 @@ protected List create(String[] elements) { @Override protected List create(String[] elements) { List fromList = Lists.newLinkedList(asList(elements)); - return Lists.transform(fromList, Functions.identity()); + return transform(fromList, Functions.identity()); } }) .named("Lists.transform, sequential access, nulls") @@ -305,7 +319,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); @@ -315,39 +329,31 @@ public void testCharactersOfIsView() { public void testNewArrayListEmpty() { 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,11 +362,11 @@ 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() { @@ -380,7 +386,7 @@ public void testNewArrayListFromIterator() { public void testNewLinkedListEmpty() { LinkedList list = Lists.newLinkedList(); - assertEquals(Collections.emptyList(), list); + assertEquals(emptyList(), list); } public void testNewLinkedListFromCollection() { @@ -393,18 +399,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 +436,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 +501,7 @@ protected Iterator newTargetIterator() { }.test(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testAsList2Small() { List list = Lists.asList("foo", "bar", new String[0]); @@ -565,7 +568,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 +576,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 +606,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 +616,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 +637,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 +651,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 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 list = transform(fromList, SOME_FUNCTION); assertTransformModifiable(list); } @@ -702,18 +695,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 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 toList = transform(fromList, SOME_FUNCTION); assertTransformView(fromList, toList); } @@ -734,46 +727,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 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 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 +810,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 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 list = transform(fromList, SOME_FUNCTION); assertTransformIterator(list); } @@ -840,9 +839,8 @@ public void testTransformIteratorSequential() { public void testTransformedSequentialIterationUsesBackingListIterationOnly() { List randomAccessList = Lists.newArrayList(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 +888,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(), @@ -955,7 +950,7 @@ public void testPartitionRandomAccessTrue() { public void testPartitionRandomAccessFalse() { List source = Lists.newLinkedList(asList(1, 2, 3)); - List> partitions = Lists.partition(source, 2); + List> partitions = partition(source, 2); assertFalse(partitions instanceof RandomAccess); assertFalse(partitions.get(0) instanceof RandomAccess); assertFalse(partitions.get(1) instanceof RandomAccess); @@ -965,7 +960,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 +984,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/MapMakerInternalMapTest.java b/android/guava-tests/test/com/google/common/collect/MapMakerInternalMapTest.java index 47fc74c82e5e..7bc01a7dc2e0 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); @@ -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(); @@ -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(); 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..9c79948d17a1 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,27 @@ 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 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 */ +/** + * @author Charles Fry + */ @GwtCompatible(emulated = true) +@J2ktIncompatible // MapMaker +@NullUnmarked public class MapMakerTest extends TestCase { - @GwtIncompatible // NullPointerTester public void testNullParameters() throws Exception { NullPointerTester tester = new NullPointerTester(); @@ -57,31 +63,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..a52b7a8f50c3 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,7 +40,6 @@ 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.List; import java.util.Map; @@ -52,13 +52,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; /** * Test suites for wrappers in {@code Maps}. * * @author Louis Wasserman */ +@NullUnmarked public class MapsCollectionTest extends TestCase { public static Test suite() { TestSuite suite = new TestSuite(); @@ -133,7 +135,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 +204,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 +271,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 @@ -561,7 +563,7 @@ static void putEntries(Map map, Entry[] entries) static final Predicate FILTER_KEYS = new Predicate() { @Override - public boolean apply(@NullableDecl String string) { + public boolean apply(@Nullable String string) { return !"banana".equals(string) && !"eggplant".equals(string); } }; @@ -569,7 +571,7 @@ public boolean apply(@NullableDecl String string) { static final Predicate FILTER_VALUES = new Predicate() { @Override - public boolean apply(@NullableDecl String string) { + public boolean apply(@Nullable String string) { return !"toast".equals(string) && !"spam".equals(string); } }; @@ -578,8 +580,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 +589,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 +597,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 +633,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); } }; @@ -669,7 +671,7 @@ protected Map 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[Map, Function]") @@ -716,7 +718,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 +761,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..61ddae77aaf9 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; @@ -63,6 +66,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}. @@ -72,13 +77,15 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked +@SuppressWarnings("JUnitIncompatibleType") // Many intentional violations here. public class MapsTest extends TestCase { private static final Comparator SOME_COMPARATOR = Collections.reverseOrder(); public void testHashMap() { HashMap map = Maps.newHashMap(); - assertEquals(Collections.emptyMap(), map); + assertEquals(emptyMap(), map); } public void testHashMapWithInitialMap() { @@ -95,17 +102,12 @@ public void testHashMapGeneralizesTypes() { original.put("a", 1); original.put("b", 2); original.put("c", 3); - HashMap map = - Maps.newHashMap((Map) original); + 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,6 +119,7 @@ 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 { @@ -126,11 +129,15 @@ public void testNewHashMapWithExpectedSize_wontGrow() throws Exception { 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 { @@ -139,14 +146,20 @@ public void testNewLinkedHashMapWithExpectedSize_wontGrow() throws Exception { 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 +181,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"); @@ -198,10 +222,9 @@ public void testCapacityForLargeSizes() { public void testLinkedHashMap() { LinkedHashMap map = Maps.newLinkedHashMap(); - assertEquals(Collections.emptyMap(), map); + assertEquals(emptyMap(), map); } - @SuppressWarnings("serial") public void testLinkedHashMapWithInitialMap() { Map map = new LinkedHashMap( @@ -249,23 +272,23 @@ 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); + assertEquals(emptyMap(), map); assertNull(map.comparator()); } 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(); @@ -275,7 +298,7 @@ public void testTreeMapDerived() { 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()) @@ -287,7 +310,7 @@ public void testTreeMapNonGeneric() { public void testTreeMapWithComparator() { TreeMap map = Maps.newTreeMap(SOME_COMPARATOR); - assertEquals(Collections.emptyMap(), map); + assertEquals(emptyMap(), map); assertSame(SOME_COMPARATOR, map.comparator()); } @@ -307,17 +330,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() { @@ -343,15 +364,11 @@ public void testEnumMapWithInitialMap() { public void testEnumMapWithInitialEmptyMap() { Map original = Maps.newHashMap(); - try { - Maps.newEnumMap(original); - fail("Empty map must result in an IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Maps.newEnumMap(original)); } public void testToStringImplWithNullKeys() throws Exception { - Map hashmap = Maps.newHashMap(); + Map<@Nullable String, String> hashmap = Maps.newHashMap(); hashmap.put("foo", "bar"); hashmap.put(null, "baz"); @@ -359,20 +376,21 @@ public void testToStringImplWithNullKeys() throws Exception { } public void testToStringImplWithNullValues() throws Exception { - Map hashmap = Maps.newHashMap(); + Map hashmap = Maps.newHashMap(); 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 +571,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 +587,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 +613,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() { @@ -743,26 +749,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() { @@ -869,31 +860,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 @@ -933,21 +910,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 +956,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(); @@ -1063,6 +1028,7 @@ public void testFromProperties() throws IOException { assertNotSame(System.getProperty("java.version"), result.get("java.version")); } + @J2ktIncompatible @GwtIncompatible // Maps.fromProperties @SuppressWarnings("serial") // never serialized public void testFromPropertiesNullKey() { @@ -1070,19 +1036,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 +1058,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 +1089,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 { @@ -1153,31 +1108,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 +1165,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); + Entry<@Nullable String, @Nullable Integer> e = immutableEntry((String) null, (Integer) null); assertNull(e.getKey()); assertNull(e.getValue()); - try { - e.setValue(null); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + 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 +1215,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); @@ -1741,92 +1391,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); + assertNull(entry); } - 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 +1458,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 +1554,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..e1cafc86b964 100644 --- a/android/guava-tests/test/com/google/common/collect/MapsTransformValuesTest.java +++ b/android/guava-tests/test/com/google/common/collect/MapsTransformValuesTest.java @@ -16,50 +16,25 @@ 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.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(Maps.newHashMap(), Functions.identity()); } @Override @@ -68,241 +43,6 @@ protected Map makePopulatedMap() { 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..d53579f14f9f 100644 --- a/android/guava-tests/test/com/google/common/collect/MapsTransformValuesUnmodifiableIteratorTest.java +++ b/android/guava-tests/test/com/google/common/collect/MapsTransformValuesUnmodifiableIteratorTest.java @@ -16,6 +16,10 @@ 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 com.google.common.annotations.GwtCompatible; import com.google.common.base.Function; import com.google.common.base.Functions; @@ -25,7 +29,8 @@ 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 +39,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public class MapsTransformValuesUnmodifiableIteratorTest extends MapInterfaceTest { // TODO(jlevy): Move shared code of this class and MapsTransformValuesTest // to a superclass. @@ -133,7 +139,7 @@ public boolean retainAll(Collection c) { @Override protected Map makeEmptyMap() { Map underlying = Maps.newHashMap(); - return Maps.transformValues( + return transformValues( new UnmodifiableIteratorMap(underlying), Functions.toStringFunction()); } @@ -143,7 +149,7 @@ protected Map makePopulatedMap() { underlying.put("a", 1); underlying.put("b", 2); underlying.put("c", 3); - return Maps.transformValues( + return transformValues( new UnmodifiableIteratorMap(underlying), Functions.toStringFunction()); } @@ -174,13 +180,13 @@ private void assertMapsEqual(Map expected, Map map) { public void testTransformEmptyMapEquality() { Map map = - Maps.transformValues(ImmutableMap.of(), Functions.toStringFunction()); + transformValues(ImmutableMap.of(), Functions.toStringFunction()); assertMapsEqual(Maps.newHashMap(), 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 +194,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(); 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")); } public void testTransformEqualityOfMapsWithNullValues() { - Map underlying = Maps.newHashMap(); + Map underlying = Maps.newHashMap(); 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; } }); @@ -251,7 +247,7 @@ public void testTransformReflectsUnderlyingMap() { 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); @@ -275,7 +271,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 +313,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 +335,29 @@ public Integer apply(Integer from) { } public void testTransformEntrySetContains() { - Map underlying = Maps.newHashMap(); + Map<@Nullable String, @Nullable Boolean> underlying = Maps.newHashMap(); 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..f3b58b220093 100644 --- a/android/guava-tests/test/com/google/common/collect/MinMaxPriorityQueueTest.java +++ b/android/guava-tests/test/com/google/common/collect/MinMaxPriorityQueueTest.java @@ -19,10 +19,15 @@ 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; @@ -47,6 +52,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}. @@ -55,9 +62,11 @@ * @author Sverre Sundsdal */ @GwtCompatible(emulated = true) +@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 public static Test suite() { TestSuite suite = new TestSuite(); @@ -67,7 +76,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 +101,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 +120,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 +137,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 +163,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 +172,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 +209,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. @@ -276,7 +292,7 @@ public void testSmallMinHeap() { 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 +331,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,11 +342,7 @@ 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. */ @@ -466,7 +474,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 +492,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,7 +501,9 @@ 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()); @@ -507,7 +518,7 @@ private > void runIterator(final List values, int ste IteratorFeature.MODIFIABLE, Lists.newLinkedList(values), IteratorTester.KnownOrder.UNKNOWN_ORDER) { - private MinMaxPriorityQueue mmHeap; + private @Nullable MinMaxPriorityQueue mmHeap; @Override protected Iterator newTargetIterator() { @@ -517,7 +528,7 @@ protected Iterator newTargetIterator() { @Override protected void verify(List elements) { - assertEquals(Sets.newHashSet(elements), Sets.newHashSet(mmHeap.iterator())); + assertEquals(newHashSet(elements), newHashSet(mmHeap.iterator())); assertIntact(mmHeap); } }; @@ -542,7 +553,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()); } @@ -741,9 +753,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 +876,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,7 +941,8 @@ private static void assertIntactUsingStartedWith( } } - private static void assertEqualsUsingSeed(long seed, Object expected, Object actual) { + private static void assertEqualsUsingSeed( + long seed, @Nullable Object expected, @Nullable Object actual) { if (!equal(actual, expected)) { // fail(), but with the JUnit-supplied message. assertEquals("Using seed " + seed, expected, actual); @@ -950,10 +950,18 @@ private static void assertEqualsUsingSeed(long seed, Object expected, Object act } private static void assertEqualsUsingStartedWith( - Collection startedWith, Object expected, Object actual) { + Collection startedWith, @Nullable Object expected, @Nullable Object actual) { if (!equal(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/MultimapBuilderTest.java b/android/guava-tests/test/com/google/common/collect/MultimapBuilderTest.java index 5528aba94c67..27b3de8d9bbd 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,6 +29,8 @@ 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}. @@ -34,27 +38,32 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@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,11 +73,27 @@ 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); } + // 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; + } + + @J2ktIncompatible @GwtIncompatible // serialization public void testSerialization() throws Exception { for (MultimapBuilderWithKeys builderWithKeys : @@ -93,6 +118,7 @@ public void testSerialization() throws Exception { } } + @J2ktIncompatible @GwtIncompatible // serialization private static void reserializeAndAssert(Object object) throws Exception { Object copy = reserialize(object); @@ -100,6 +126,7 @@ private static void reserializeAndAssert(Object object) throws Exception { assertEquals(object.getClass(), copy.getClass()); } + @J2ktIncompatible @GwtIncompatible // serialization private static Object reserialize(Object object) throws Exception { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 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..27cd588bd1ff 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,13 @@ package com.google.common.collect; +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.Maps.newHashMap; +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; @@ -30,7 +36,6 @@ 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; @@ -64,6 +69,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 +77,7 @@ * @author Jared Levy */ @GwtIncompatible // suite // TODO(cpovirk): set up collect/gwt/suites version +@NullUnmarked public class MultimapsCollectionTest extends TestCase { private static final Feature[] FOR_MAP_FEATURES_ONE = { @@ -175,11 +182,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 +222,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 +234,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 +363,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 +482,7 @@ public SampleElements> samples() { @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override @@ -577,8 +567,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 +588,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 +608,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 +629,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 +650,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 +670,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 +694,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 +714,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..9066880cf25b 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,7 @@ import java.util.Collection; import java.util.Map; import java.util.Map.Entry; +import org.jspecify.annotations.NullUnmarked; /** * Tests for Multimaps.filterEntries().asMap(). @@ -28,6 +29,7 @@ * @author Jared Levy */ @GwtIncompatible(value = "untested") +@NullUnmarked public class MultimapsFilterEntriesAsMapTest extends AbstractMultimapAsMapImplementsMapTest { private static final Predicate> PREDICATE = new Predicate>() { 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..674b17f0fa32 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; @@ -56,7 +66,8 @@ 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}. @@ -64,6 +75,7 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class MultimapsTest extends TestCase { private static final Comparator INT_COMPARATOR = @@ -108,9 +120,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 +152,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 +168,7 @@ public void testUnmodifiableTreeMultimap() { checkUnmodifiableMultimap(TreeMultimap.create(), false, "null", 42); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerializingUnmodifiableTreeMultimap() { Multimap unmodifiable = @@ -161,16 +177,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 +197,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 +247,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 +263,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 +273,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); @@ -294,11 +302,11 @@ private static void checkUnmodifiableMultimap( } /** 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 +324,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( @@ -404,28 +417,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 +435,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,6 +448,7 @@ public void testForMap() { assertEquals(multimapView, ArrayListMultimap.create()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testForMapSerialization() { Map map = Maps.newHashMap(); @@ -464,10 +465,10 @@ public void testForMapRemoveAll() { 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")); } @@ -477,7 +478,7 @@ public void testForMapAsMap() { map.put("foo", 1); map.put("bar", 2); Map> asMap = Multimaps.forMap(map).asMap(); - assertEquals(Collections.singleton(1), asMap.get("foo")); + assertEquals(singleton(1), asMap.get("foo")); assertNull(asMap.get("cow")); assertTrue(asMap.containsKey("foo")); assertFalse(asMap.containsKey("cow")); @@ -485,15 +486,15 @@ public void testForMapAsMap() { 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", singletonList(1)))); + assertFalse(entries.remove(Maps.immutableEntry("foo", 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", 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,7 +502,7 @@ 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() { @@ -577,18 +578,10 @@ 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)); } @@ -610,7 +603,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 +624,7 @@ public void testNewMultimapValueCollectionMatchesList() { assertTrue(multimap.get(Color.BLUE) instanceof List); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testNewMultimapSerialization() { CountingSupplier> factory = new QueueSupplier(); @@ -665,6 +660,7 @@ public void testNewListMultimap() { assertTrue(multimap.asMap() instanceof SortedMap); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testNewListMultimapSerialization() { CountingSupplier> factory = new ListSupplier(); @@ -696,6 +692,7 @@ public void testNewSetMultimap() { assertEquals(Sets.newHashSet(4, 3, 1), multimap.get(Color.BLUE)); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testNewSetMultimapSerialization() { CountingSupplier> factory = new SetSupplier(); @@ -729,6 +726,7 @@ public void testNewSortedSetMultimap() { assertEquals(INT_COMPARATOR, multimap.valueComparator()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testNewSortedSetMultimapSerialization() { CountingSupplier> factory = new SortedSetSupplier(); @@ -795,21 +793,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 +901,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 +922,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 +932,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 +948,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 +963,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..eac7058b374b 100644 --- a/android/guava-tests/test/com/google/common/collect/MultisetsCollectionTest.java +++ b/android/guava-tests/test/com/google/common/collect/MultisetsCollectionTest.java @@ -16,7 +16,14 @@ 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; @@ -33,6 +40,7 @@ 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,7 @@ * @author Jared Levy */ @GwtIncompatible // suite // TODO(cpovirk): set up collect/gwt/suites version +@NullUnmarked public class MultisetsCollectionTest extends TestCase { public static Test suite() { TestSuite suite = new TestSuite(); @@ -50,7 +59,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 +120,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 +148,7 @@ protected Multiset create(String[] elements) { @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } }; @@ -163,7 +172,7 @@ protected Multiset create(String[] elements) { multiset2.add(elements[i]); } } - return Multisets.union(multiset1, multiset2); + return union(multiset1, multiset2); } }; } @@ -193,7 +202,7 @@ protected Multiset create(String[] elements) { multiset2.add(elements[1], 2); } } - return Multisets.intersection(multiset1, multiset2); + return intersection(multiset1, multiset2); } }; } @@ -212,7 +221,7 @@ protected Multiset create(String[] elements) { multiset2.add(elements[i]); } } - return Multisets.sum(multiset1, multiset2); + return sum(multiset1, multiset2); } }; } @@ -233,7 +242,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 +250,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() { 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..63e01282cfe1 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(final E element, final 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..0921dc79dea3 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}. @@ -35,6 +42,7 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@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,71 +184,71 @@ 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(); } @@ -248,16 +256,16 @@ public void testRemoveOccurrencesMultisetIterable() { @SuppressWarnings("deprecation") 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..f1d2e94fa302 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,8 @@ package com.google.common.collect; +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,12 +29,14 @@ 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 { public static Test suite() { TestSuite suite = new TestSuite(); @@ -44,7 +48,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) { @@ -83,11 +87,7 @@ public void testConstraint() { * 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"); } @@ -104,11 +104,7 @@ 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)); 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..947dda86b406 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,15 @@ 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.Map; import java.util.TreeMap; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Test cases for {@link Tables#newCustomTable}. @@ -29,10 +32,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 @@ -41,7 +45,7 @@ public TreeMap get() { } }; Map> backingMap = Maps.newLinkedHashMap(); - Table table = Tables.newCustomTable(backingMap, factory); + 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..46781fcfeae2 100644 --- a/android/guava-tests/test/com/google/common/collect/ObjectArraysTest.java +++ b/android/guava-tests/test/com/google/common/collect/ObjectArraysTest.java @@ -20,11 +20,13 @@ 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.Arrays; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@code ObjectArrays}. @@ -32,8 +34,10 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@NullMarked public class ObjectArraysTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); @@ -41,42 +45,43 @@ 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]); } + @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]); } - 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]); } - 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); @@ -114,14 +119,14 @@ 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(Lists.newArrayList(1)); - doTestToArrayImpl1(Lists.newArrayList(1, null, 3)); + doTestToArrayImpl1(Lists.<@Nullable Integer>newArrayList(1, null, 3)); } private void doTestToArrayImpl1(List list) { @@ -139,15 +144,17 @@ public void testToArrayImpl2() { 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) { Integer[] starting = Arrays.copyOf(array1, array1.length); Integer[] array2 = Arrays.copyOf(array1, array1.length); - Object[] reference = list.toArray(array1); + // TODO b/283448200 - Remove temporary variable when Kotlin smartcast issue is resolved. + Integer[] array1Tmp = array1; + Object[] reference = list.toArray(array1Tmp); Object[] target = ObjectArrays.toArrayImpl(list, array2); 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..f2338011e98c 100644 --- a/android/guava-tests/test/com/google/common/collect/OrderingTest.java +++ b/android/guava-tests/test/com/google/common/collect/OrderingTest.java @@ -17,20 +17,28 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Iterators.singletonIterator; import static com.google.common.collect.Lists.newArrayList; +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.Arrays; @@ -41,7 +49,9 @@ 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}. @@ -49,13 +59,14 @@ * @author Jesse Wilson */ @GwtCompatible(emulated = true) +@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,26 +83,31 @@ 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() + .reverse() + .lexicographical() + .reverse() + .>nullsLast(); + List<@Nullable Integer> list1 = Lists.newArrayList(); + 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), @@ -103,22 +119,10 @@ public void testComplicatedOrderingExample() { 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()); } @@ -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 an 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,22 +187,20 @@ 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(); for (int i = 0; i < 50; i++) { @@ -205,15 +208,16 @@ public void testArbitrary_withoutCollisions() { } 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(); for (int i = 0; i < 50; 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()) @@ -445,29 +448,30 @@ public void testBinarySearch() { } 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 +480,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 +490,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 +523,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 +562,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 +576,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 +601,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 +609,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 +617,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 +625,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 +651,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 +665,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 +677,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); } @@ -707,15 +709,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 +727,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 +737,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,7 +836,7 @@ public int hashCode() { } @Override - public boolean equals(Object other) { + public boolean equals(@Nullable Object other) { return other instanceof NumberOrdering; } @@ -868,13 +871,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); } @@ -887,18 +891,18 @@ private static void testExhaustively( checkArgument( strictlyOrderedElements.length >= 3, "strictlyOrderedElements " + "requires at least 3 elements"); - List list = Arrays.asList(strictlyOrderedElements); + 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 +920,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 +932,7 @@ private static class Scenario { } void testCompareTo() { - Helpers.testComparator(ordering, strictlyOrderedList); + testComparator(ordering, strictlyOrderedList); } void testIsOrdered() { @@ -936,7 +940,7 @@ 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); shuffledList = shuffledCopy(shuffledList, new Random(5)); @@ -978,7 +982,8 @@ void testSortedCopy() { 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,7 +996,7 @@ void testSortedCopy() { private enum OrderingMutation { REVERSE { @Override - Scenario mutate(Scenario scenario) { + Scenario mutate(Scenario scenario) { List newList = Lists.newArrayList(scenario.strictlyOrderedList); Collections.reverse(newList); return new Scenario(scenario.ordering.reverse(), newList, scenario.emptyArray); @@ -999,8 +1004,7 @@ Scenario mutate(Scenario scenario) { }, 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,7 +1016,7 @@ Scenario mutate(Scenario scenario) { }, NULLS_LAST { @Override - Scenario mutate(Scenario scenario) { + Scenario mutate(Scenario scenario) { List newList = Lists.newArrayList(); for (T t : scenario.strictlyOrderedList) { if (t != null) { @@ -1025,12 +1029,12 @@ Scenario mutate(Scenario scenario) { }, ON_RESULT_OF { @Override - Scenario mutate(final Scenario scenario) { + Scenario mutate(final 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); } }); @@ -1042,9 +1046,9 @@ public T apply(@NullableDecl Integer from) { } }, COMPOUND_THIS_WITH_NATURAL { - @SuppressWarnings("unchecked") // raw array + @SuppressWarnings("unchecked") // generic arrays @Override - Scenario mutate(Scenario scenario) { + Scenario mutate(Scenario scenario) { List> composites = Lists.newArrayList(); for (T t : scenario.strictlyOrderedList) { composites.add(new Composite(t, 1)); @@ -1055,13 +1059,14 @@ 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) { + Scenario mutate(Scenario scenario) { List> composites = Lists.newArrayList(); for (T t : scenario.strictlyOrderedList) { composites.add(new Composite(t, 1)); @@ -1070,37 +1075,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) { + Scenario mutate(Scenario scenario) { List> words = Lists.newArrayList(); 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 +1119,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 +1132,7 @@ public T apply(Composite from) { } } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); @@ -1135,7 +1142,7 @@ public void testNullPointerExceptions() { tester.testAllPublicInstanceMethods(Ordering.usingToString().nullsFirst()); } - private static List shuffledCopy(List in, Random random) { + private static List shuffledCopy(List in, Random random) { List mutable = newArrayList(in); List out = newArrayList(); while (!mutable.isEmpty()) { 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..b725ad4fc71f 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}. @@ -38,6 +42,7 @@ */ @SuppressWarnings("serial") // No serialization is used in this test @GwtCompatible(emulated = true) +@NullMarked public class PeekingIteratorTest extends TestCase { /** @@ -47,11 +52,11 @@ 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 static class PeekingIteratorTester extends IteratorTester { private Iterable master; - private List targetList; + private @Nullable List targetList; public PeekingIteratorTester(Collection master) { super(master.size() + 3, MODIFIABLE, master, IteratorTester.KnownOrder.KNOWN_ORDER); @@ -73,7 +78,7 @@ protected void verify(List elements) { } } - private void actsLikeIteratorHelper(final List list) { + private void actsLikeIteratorHelper(final 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(); + Iterator iterator = unmodifiableList(list).iterator(); return Iterators.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,20 +109,15 @@ 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); - try { - peekingIterator.peek(); - fail("Should throw NoSuchElementException if nothing to peek()"); - } catch (NoSuchElementException e) { - /* expected */ - } + assertThrows(NoSuchElementException.class, () -> peekingIterator.peek()); } public void testPeekDoesntChangeIteration() { @@ -143,24 +143,9 @@ 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() { @@ -172,12 +157,7 @@ public void testCantRemoveAfterPeek() { 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..277e9eceb1f3 100644 --- a/android/guava-tests/test/com/google/common/collect/QueuesTest.java +++ b/android/guava-tests/test/com/google/common/collect/QueuesTest.java @@ -24,6 +24,7 @@ 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.util.Collection; @@ -40,13 +41,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. @@ -122,11 +125,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)); @@ -154,11 +157,11 @@ private void testDrainTimesOut(BlockingQueue q) throws Exception { 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,21 +170,21 @@ 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)); @@ -196,11 +199,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,17 +215,18 @@ 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) { + private void testDrainUninterruptiblyDoesNotThrow(final BlockingQueue q) { final 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(); @@ -238,23 +242,13 @@ 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()); } @@ -287,6 +281,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)); @@ -303,7 +298,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 +310,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++) { 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..b0db5296a8cd 100644 --- a/android/guava-tests/test/com/google/common/collect/RangeTest.java +++ b/android/guava-tests/test/com/google/common/collect/RangeTest.java @@ -19,19 +19,22 @@ 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.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}. @@ -39,6 +42,7 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@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() { @@ -601,10 +556,10 @@ public void testEquals() { @GwtIncompatible // TODO(b/148207871): Restore once Eclipse compiler no longer flakes for this. 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 +605,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..93f5a5c94358 --- /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(emulated = true) +@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..86d67d5ad1e2 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); } 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 index 7a1ec3a6590c..f17d0ee3b37e 100644 --- a/android/guava-tests/test/com/google/common/collect/SetOperationsTest.java +++ b/android/guava-tests/test/com/google/common/collect/SetOperationsTest.java @@ -17,10 +17,12 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Sets.newHashSet; 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.SetTestSuiteBuilder; import com.google.common.collect.testing.TestStringSetGenerator; import com.google.common.collect.testing.features.CollectionFeature; @@ -30,6 +32,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Unit tests for {@link Sets#union}, {@link Sets#intersection} and {@link Sets#difference}. @@ -37,7 +40,9 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@NullMarked public class SetOperationsTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -61,7 +66,7 @@ protected Set create(String[] elements) { @Override protected Set create(String[] elements) { checkArgument(elements.length == 1); - return Sets.union(Sets.newHashSet(elements), Sets.newHashSet(elements)); + return Sets.union(Sets.newHashSet(elements), newHashSet(elements)); } }) .named("singleton U itself") @@ -73,7 +78,7 @@ protected Set create(String[] elements) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return Sets.union(Sets.newHashSet(), Sets.newHashSet(elements)); + return Sets.union(Sets.newHashSet(), newHashSet(elements)); } }) .named("empty U set") @@ -86,7 +91,7 @@ protected Set create(String[] elements) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return Sets.union(Sets.newHashSet(elements), Sets.newHashSet()); + return Sets.union(newHashSet(elements), Sets.newHashSet()); } }) .named("set U empty") @@ -117,7 +122,7 @@ protected Set create(String[] elements) { protected Set create(String[] elements) { checkArgument(elements.length == 3); return Sets.union( - Sets.newHashSet(elements[0]), Sets.newHashSet(elements[1], elements[2])); + newHashSet(elements[0]), newHashSet(elements[1], elements[2])); } }) .named("union of disjoint") @@ -131,7 +136,7 @@ protected Set create(String[] elements) { protected Set create(String[] elements) { return Sets.union( Sets.newHashSet(elements[0], elements[1]), - Sets.newHashSet(elements[1], elements[2])); + newHashSet(elements[1], elements[2])); } }) .named("venn") @@ -156,8 +161,7 @@ protected Set create(String[] elements) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return Sets.intersection( - Sets.newHashSet(), Sets.newHashSet((String) null)); + return Sets.intersection(Sets.newHashSet(), newHashSet((String) null)); } }) .named("empty & singleton") @@ -170,7 +174,7 @@ protected Set create(String[] elements) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return Sets.intersection(Sets.newHashSet("a", "b"), Sets.newHashSet("c", "d")); + return Sets.intersection(newHashSet("a", "b"), newHashSet("c", "d")); } }) .named("intersection of disjoint") @@ -183,7 +187,7 @@ protected Set create(String[] elements) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return Sets.intersection(Sets.newHashSet(elements), Sets.newHashSet(elements)); + return Sets.intersection(newHashSet(elements), newHashSet(elements)); } }) .named("set & itself") @@ -197,8 +201,7 @@ protected Set create(String[] elements) { @Override protected Set create(String[] elements) { return Sets.intersection( - Sets.newHashSet("a", elements[0], "b"), - Sets.newHashSet("c", elements[0], "d")); + newHashSet("a", elements[0], "b"), newHashSet("c", elements[0], "d")); } }) .named("intersection with overlap of one") @@ -223,7 +226,7 @@ protected Set create(String[] elements) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return Sets.difference(Sets.newHashSet("a"), Sets.newHashSet("a")); + return Sets.difference(newHashSet("a"), newHashSet("a")); } }) .named("singleton - itself") @@ -236,8 +239,8 @@ protected Set create(String[] elements) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - Set set = Sets.newHashSet("b", "c"); - Set other = Sets.newHashSet("a", "b", "c", "d"); + Set set = newHashSet("b", "c"); + Set other = newHashSet("a", "b", "c", "d"); return Sets.difference(set, other); } }) @@ -251,8 +254,8 @@ protected Set create(String[] elements) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - Set set = Sets.newHashSet(elements); - Set other = Sets.newHashSet("wz", "xq"); + Set set = newHashSet(elements); + Set other = newHashSet("wz", "xq"); set.addAll(other); other.add("pq"); return Sets.difference(set, other); @@ -270,7 +273,7 @@ protected Set create(String[] elements) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return Sets.difference(Sets.newHashSet(elements), Sets.newHashSet()); + return Sets.difference(newHashSet(elements), newHashSet()); } }) .named("set - empty") @@ -284,99 +287,97 @@ protected Set create(String[] elements) { @Override protected Set create(String[] elements) { return Sets.difference( - Sets.newHashSet(elements), Sets.newHashSet("xx", "xq")); + Sets.newHashSet(elements), newHashSet("xx", "xq")); } }) .named("set - disjoint") .withFeatures(CollectionSize.ANY, CollectionFeature.ALLOWS_NULL_VALUES) .createTestSuite()); - suite.addTestSuite(MoreTests.class); + suite.addTestSuite(SetOperationsTest.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()); - } + Set friends; + Set enemies; + + @Override + public void setUp() { + friends = newHashSet("Tom", "Joe", "Dave"); + enemies = 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 = newHashSet("Tom", "Joe", "Dave"); + Set enemies = 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 = newHashSet("Tom", "Joe", "Dave"); + Set enemies = 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 = newHashSet("Tom", "Joe", "Dave"); + Set enemies = 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/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..8d6b85ab517f --- /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) { + 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); + } +} 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..721e5397d5dd 100644 --- a/android/guava-tests/test/com/google/common/collect/SetsTest.java +++ b/android/guava-tests/test/com/google/common/collect/SetsTest.java @@ -17,6 +17,8 @@ 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; @@ -27,11 +29,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 +57,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 +80,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}. @@ -85,6 +90,7 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class SetsTest extends TestCase { private static final IteratorTester.KnownOrder KNOWN_ORDER = @@ -92,7 +98,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,10 +108,11 @@ 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 public static Test suite() { TestSuite suite = new TestSuite(); @@ -116,7 +123,7 @@ 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") @@ -132,12 +139,12 @@ protected Set create(String[] elements) { // Remove last element, if size > 1 Set set1 = (size > 1) - ? Sets.newHashSet(Arrays.asList(elements).subList(0, size - 1)) - : Sets.newHashSet(elements); + ? newHashSet(asList(elements).subList(0, size - 1)) + : newHashSet(elements); // Remove first element, if size > 0 Set set2 = (size > 0) - ? Sets.newHashSet(Arrays.asList(elements).subList(1, size)) + ? newHashSet(asList(elements).subList(1, size)) : Sets.newHashSet(); return Sets.union(set1, set2); } @@ -151,9 +158,9 @@ protected Set create(String[] elements) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - Set set1 = Sets.newHashSet(elements); + Set set1 = newHashSet(elements); set1.add(samples().e3()); - Set set2 = Sets.newHashSet(elements); + Set set2 = newHashSet(elements); set2.add(samples().e4()); return Sets.intersection(set1, set2); } @@ -167,9 +174,9 @@ protected Set create(String[] elements) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - Set set1 = Sets.newHashSet(elements); + Set set1 = newHashSet(elements); set1.add(samples().e3()); - Set set2 = Sets.newHashSet(samples().e3()); + Set set2 = newHashSet(samples().e3()); return Sets.difference(set1, set2); } }) @@ -183,7 +190,7 @@ protected Set create(String[] elements) { @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 +204,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,6 +225,7 @@ public List order(List insertionOrder) { return suite; } + @J2ktIncompatible @GwtIncompatible // suite private static Test testsForFilter() { return SetTestSuiteBuilder.using( @@ -241,6 +249,7 @@ public Set create(String[] elements) { .createTestSuite(); } + @J2ktIncompatible @GwtIncompatible // suite private static Test testsForFilterNoNulls() { TestSuite suite = new TestSuite(); @@ -292,6 +301,7 @@ public List order(List insertionOrder) { return suite; } + @J2ktIncompatible @GwtIncompatible // suite private static Test testsForFilterFiltered() { return SetTestSuiteBuilder.using( @@ -325,22 +335,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,6 +366,7 @@ public void testImmutableEnumSet_fromIterable() { assertThat(two).containsExactly(SomeEnum.B, SomeEnum.D).inOrder(); } + @J2ktIncompatible @GwtIncompatible // java serialization not supported in GWT. public void testImmutableEnumSet_deserializationMakesDefensiveCopy() throws Exception { ImmutableSet original = Sets.immutableEnumSet(SomeEnum.A, SomeEnum.B); @@ -377,6 +382,7 @@ public void testImmutableEnumSet_deserializationMakesDefensiveCopy() throws Exce assertTrue(deserialized.contains(SomeEnum.A)); } + @J2ktIncompatible @GwtIncompatible // java serialization not supported in GWT. private static byte[] serializeWithBackReference(Object original, int handleOffset) throws IOException { @@ -395,7 +401,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 +431,22 @@ public void testNewEnumSet_iterable() { } public void testNewHashSetEmpty() { - HashSet set = Sets.newHashSet(); + 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); + 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 +461,7 @@ public void testNewHashSetWithExpectedSizeLarge() { } public void testNewHashSetFromIterator() { - HashSet set = Sets.newHashSet(SOME_COLLECTION.iterator()); + HashSet set = newHashSet(SOME_COLLECTION.iterator()); verifySetContents(set, SOME_COLLECTION); } @@ -535,14 +541,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 +571,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() @@ -642,6 +663,7 @@ public void testNewSetFromMap() { verifySetContents(set, SOME_COLLECTION); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testNewSetFromMapSerialization() { Set set = Sets.newSetFromMap(new LinkedHashMap()); @@ -653,44 +675,32 @@ public void testNewSetFromMapSerialization() { 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 +709,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 +739,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 +748,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 +777,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() { @@ -832,7 +845,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,11 +864,7 @@ 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 @@ -907,27 +916,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() { @@ -1009,7 +1015,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 +1025,7 @@ private static void assertPowerSetSize(int i, Object... elements) { } private static void checkHashCode(Set set) { - assertEquals(Sets.newHashSet(set).hashCode(), set.hashCode()); + assertEquals(newHashSet(set).hashCode(), set.hashCode()); } public void testCombinations() { @@ -1076,7 +1083,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 +1105,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 +1130,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 +1147,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 +1173,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 +1271,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..e6244281876d 100644 --- a/android/guava-tests/test/com/google/common/collect/SimpleAbstractMultisetTest.java +++ b/android/guava-tests/test/com/google/common/collect/SimpleAbstractMultisetTest.java @@ -15,9 +15,11 @@ 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.annotations.J2ktIncompatible; import com.google.common.base.Objects; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; @@ -31,7 +33,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 {@link AbstractMultiset}. @@ -41,7 +44,9 @@ */ @SuppressWarnings("serial") // No serialization is used in this test @GwtCompatible(emulated = true) +@NullMarked public class SimpleAbstractMultisetTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -65,6 +70,7 @@ protected Multiset create(String[] elements) { return suite; } + @SuppressWarnings("ModifiedButNotUsed") public void testFastAddAllMultiset() { final AtomicInteger addCalls = new AtomicInteger(); Multiset multiset = @@ -84,15 +90,12 @@ 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 { + private static class NoRemoveMultiset extends AbstractMultiset + implements Serializable { final Map backingMap = Maps.newHashMap(); @Override @@ -106,7 +109,7 @@ 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)) { return entry.getCount(); @@ -116,7 +119,7 @@ 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) { 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/android/guava-tests/test/com/google/common/collect/SingletonImmutableSortedMapMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/SingletonImmutableSortedMapMapInterfaceTest.java new file mode 100644 index 000000000000..ae4f7d8962fe --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/SingletonImmutableSortedMapMapInterfaceTest.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 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..2dded744ce1d 100644 --- a/android/guava-tests/test/com/google/common/collect/SingletonImmutableTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/SingletonImmutableTableTest.java @@ -16,12 +16,14 @@ 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 org.jspecify.annotations.NullMarked; /** * Tests {@link SingletonImmutableTable}. @@ -29,6 +31,7 @@ * @author Gregory Kick */ @GwtCompatible(emulated = true) +@NullMarked public class SingletonImmutableTableTest extends AbstractImmutableTableTest { private final ImmutableTable testTable = new SingletonImmutableTable<>('a', 1, "blah"); @@ -38,7 +41,7 @@ public void testHashCode() { } 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() { 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..797f9dc6251d 100644 --- a/android/guava-tests/test/com/google/common/collect/SortedListsTest.java +++ b/android/guava-tests/test/com/google/common/collect/SortedListsTest.java @@ -16,11 +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.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. @@ -28,6 +30,7 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@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..3f1950ccd40b 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedBiMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedBiMapTest.java @@ -29,12 +29,15 @@ 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 { public static TestSuite suite() { @@ -78,7 +81,6 @@ protected BiMap create() { 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())); @@ -111,7 +113,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..6f0f7a734cec 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedDequeTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedDequeTest.java @@ -21,12 +21,15 @@ import java.util.Deque; import java.util.Iterator; 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() { @@ -38,7 +41,7 @@ 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 + public final Object mutex = new Object[0]; // something Serializable @Override public boolean offer(E o) { @@ -47,7 +50,7 @@ public boolean offer(E o) { } @Override - public E poll() { + public @Nullable E poll() { assertTrue(Thread.holdsLock(mutex)); return delegate.poll(); } @@ -65,7 +68,7 @@ public boolean remove(Object object) { } @Override - public E peek() { + public @Nullable E peek() { assertTrue(Thread.holdsLock(mutex)); return delegate.peek(); } @@ -186,13 +189,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 +213,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 +257,7 @@ public Iterator descendingIterator() { private static final long serialVersionUID = 0; } + @SuppressWarnings("CheckReturnValue") public void testHoldsLockOnAllOperations() { create().element(); create().offer("foo"); @@ -263,8 +267,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..b1ab77b6ff46 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); @@ -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() { @@ -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..7f7508c7155b 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,13 +35,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 {@code Synchronized#multimap}. * * @author Mike Bostock */ +@NullUnmarked public class SynchronizedMultimapTest extends TestCase { public static Test suite() { @@ -75,7 +79,7 @@ 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 + public final Object mutex = new Object[0]; // something Serializable @Override protected SetMultimap delegate() { @@ -89,7 +93,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 +117,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 +148,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 +160,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 +193,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 +207,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 +223,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 +247,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 +256,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..1a75965c8dcc 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedNavigableMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedNavigableMapTest.java @@ -33,12 +33,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 +68,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 +113,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 +137,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 +166,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 +202,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(); } @@ -260,7 +263,7 @@ public static TestSuite suite() { 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) { @@ -329,14 +332,14 @@ public void testFloorKey() { create().floorKey("a"); } - 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); @@ -384,28 +387,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..962e7512782c 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedNavigableSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedNavigableSetTest.java @@ -32,14 +32,17 @@ 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() { @@ -51,7 +54,7 @@ protected NavigableSet create() { static class TestSet extends SynchronizedSetTest.TestSet implements NavigableSet { - TestSet(NavigableSet delegate, Object mutex) { + TestSet(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(); } @@ -194,50 +197,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..4f236aa7b84d 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedQueueTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedQueueTest.java @@ -21,12 +21,15 @@ import java.util.Iterator; 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() { @@ -38,7 +41,7 @@ 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 + public final Object mutex = new Object[0]; // something Serializable @Override public boolean offer(E o) { @@ -47,7 +50,7 @@ public boolean offer(E o) { } @Override - public E poll() { + public @Nullable E poll() { assertTrue(Thread.holdsLock(mutex)); return delegate.poll(); } @@ -65,7 +68,7 @@ public boolean remove(Object object) { } @Override - public E peek() { + public @Nullable E peek() { assertTrue(Thread.holdsLock(mutex)); return delegate.peek(); } @@ -152,6 +155,7 @@ public T[] toArray(T[] array) { private static final long serialVersionUID = 0; } + @SuppressWarnings("CheckReturnValue") public void testHoldsLockOnAllOperations() { create().element(); create().offer("foo"); @@ -161,8 +165,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..bd6295f2ad2a 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedSetTest.java @@ -29,16 +29,18 @@ import java.util.Set; import junit.framework.Test; import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code Synchronized#set}. * * @author Mike Bostock */ +@NullUnmarked 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( @@ -82,7 +84,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); } @@ -94,7 +96,7 @@ public int hashCode() { } @Override - public boolean add(@NullableDecl E o) { + public boolean add(@Nullable E o) { assertTrue(Thread.holdsLock(mutex)); return super.add(o); } @@ -112,7 +114,7 @@ public void clear() { } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { assertTrue(Thread.holdsLock(mutex)); return super.contains(o); } @@ -129,10 +131,27 @@ public boolean isEmpty() { return super.isEmpty(); } - /* Don't test iterator(); it may or may not hold the mutex. */ + /* + * 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(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { assertTrue(Thread.holdsLock(mutex)); return super.remove(o); } 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..a80315812249 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 + public 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..6f1d51a82c08 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,16 @@ 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.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. @@ -54,20 +61,25 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@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 +87,10 @@ public class TableCollectionTest extends TestCase { CollectionFeature.ALLOWS_NULL_QUERIES }; + @J2ktIncompatible @GwtIncompatible // suite 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 +142,7 @@ protected SortedSet create(String[] elements) { @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } }) @@ -174,7 +158,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 +172,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 +231,7 @@ protected Set create(String[] elements) { @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } }) @@ -262,7 +246,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 +260,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; } }) @@ -363,7 +347,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 +364,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 +380,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,11 +393,11 @@ 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 @@ -486,7 +470,7 @@ Table createTable() { @Override Table createTable() { Table original = TreeBasedTable.create(); - return Tables.transpose(original); + return transpose(original); } }) .named("TransposedTable.cellSet") @@ -513,7 +497,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 +512,7 @@ public Set> create(Object... elements) { new TestCellSetGenerator() { @Override Table createTable() { - return Tables.unmodifiableTable( - HashBasedTable.create()); + return unmodifiableTable(HashBasedTable.create()); } @Override @@ -541,7 +524,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 +536,7 @@ public Set> create(Object... elements) { new TestCellSetGenerator() { @Override RowSortedTable createTable() { - return Tables.unmodifiableRowSortedTable( + return unmodifiableRowSortedTable( TreeBasedTable.create()); } @@ -566,7 +549,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 +603,7 @@ protected Set create(String[] elements) { @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } }) @@ -635,9 +618,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 +632,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 +646,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 +685,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 +752,7 @@ protected Integer getValueNotInPopulatedMap() { } } - private abstract static class RowTests extends MapTests { + abstract static class RowTests extends MapTests { RowTests( boolean allowsNullValues, boolean supportsPut, @@ -798,138 +780,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 +816,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> { @@ -1110,13 +846,12 @@ protected Map getValueNotInPopulatedMap() { @Override public void testRemove() { final Map> map; - final String keyToRemove; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { return; } - keyToRemove = map.keySet().iterator().next(); + final String keyToRemove = map.keySet().iterator().next(); if (supportsRemove) { int initialSize = map.size(); map.get(keyToRemove); @@ -1126,17 +861,13 @@ 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); } } - private abstract static class RowMapTests extends MapMapTests { + abstract static class RowMapTests extends MapMapTests { RowMapTests( boolean allowsNullValues, boolean supportsRemove, @@ -1166,208 +897,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 +930,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/TablesTest.java b/android/guava-tests/test/com/google/common/collect/TablesTest.java index 1cce12f27fb8..fbbdb5149228 100644 --- a/android/guava-tests/test/com/google/common/collect/TablesTest.java +++ b/android/guava-tests/test/com/google/common/collect/TablesTest.java @@ -16,12 +16,16 @@ 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}. @@ -29,43 +33,54 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@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..35d32ad17daf 100644 --- a/android/guava-tests/test/com/google/common/collect/TablesTransformValuesTest.java +++ b/android/guava-tests/test/com/google/common/collect/TablesTransformValuesTest.java @@ -17,10 +17,15 @@ 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}. @@ -28,28 +33,30 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) -public class TablesTransformValuesTest extends AbstractTableTest { +@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-tests/test/com/google/common/collect/TablesTransposeColumnTest.java b/android/guava-tests/test/com/google/common/collect/TablesTransposeColumnTest.java new file mode 100644 index 000000000000..f8e67f990829 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TablesTransposeColumnTest.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.ColumnTests; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@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..5f28508d8928 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() { @@ -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/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowTest.java b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowTest.java new file mode 100644 index 000000000000..990732ee3c67 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowTest.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.RowTests; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class TreeBasedTableRowTest extends RowTests { + public TreeBasedTableRowTest() { + super(false, true, true, true, true); + } + + @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..cd14b4b87359 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}. @@ -43,12 +47,13 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) -public class TreeBasedTableTest extends AbstractTableTest { +@NullMarked +public class TreeBasedTableTest extends AbstractTableTest { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(TreeBasedTableTest.class); - suite.addTestSuite(TreeRowTest.class); suite.addTest( SortedMapTestSuiteBuilder.using( new TestStringSortedMapGenerator() { @@ -73,58 +78,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 +94,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 +126,7 @@ public void testCreateCopy() { assertEquals(original, table); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); @@ -247,25 +201,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 +250,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 +260,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 +289,7 @@ public void testColumnKeySet_isSortedWithRealComparator() { table = create( String.CASE_INSENSITIVE_ORDER, - Ordering.natural().reverse(), + Ordering.natural().reverse(), "a", 2, 'X', @@ -400,13 +354,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 +371,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..7622c411f9f3 100644 --- a/android/guava-tests/test/com/google/common/collect/TreeMultimapExplicitTest.java +++ b/android/guava-tests/test/com/google/common/collect/TreeMultimapExplicitTest.java @@ -16,18 +16,22 @@ 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. @@ -35,17 +39,18 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@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..bd4f2b945a28 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,6 +52,7 @@ 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. @@ -57,8 +60,10 @@ * @author Jared Levy */ @GwtCompatible(emulated = true) +@NullMarked public class TreeMultimapNaturalTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -141,27 +146,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 +192,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 +225,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 +248,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 +288,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 +323,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 +340,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 +349,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 +405,7 @@ public void testComparators() { assertEquals(Ordering.natural(), multimap.valueComparator()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testExplicitComparatorSerialization() { TreeMultimap multimap = createPopulate(); @@ -417,6 +416,7 @@ public void testExplicitComparatorSerialization() { assertEquals(multimap.valueComparator(), copy.valueComparator()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testTreeMultimapDerived() { TreeMultimap multimap = TreeMultimap.create(); @@ -443,6 +443,7 @@ public void testTreeMultimapDerived() { SerializableTester.reserializeAndAssert(multimap); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testTreeMultimapNonGeneric() { TreeMultimap multimap = TreeMultimap.create(); @@ -500,6 +501,7 @@ public void testTailSetClear() { assertEquals(4, multimap.keys().size()); } + @J2ktIncompatible @GwtIncompatible // reflection public void testKeySetBridgeMethods() { for (Method m : TreeMultimap.class.getMethods()) { @@ -510,6 +512,7 @@ public void testKeySetBridgeMethods() { fail("No bridge method found"); } + @J2ktIncompatible @GwtIncompatible // reflection public void testAsMapBridgeMethods() { for (Method m : TreeMultimap.class.getMethods()) { @@ -519,6 +522,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..05453e85e279 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,6 @@ 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.Collections; import java.util.Comparator; import java.util.List; @@ -40,6 +41,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 TreeMultiset}. @@ -47,8 +50,10 @@ * @author Neal Kanodia */ @GwtCompatible(emulated = true) +@NullMarked public class TreeMultisetTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -57,7 +62,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,7 +109,7 @@ 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 @@ -142,7 +147,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 +217,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 +237,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 +288,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 +300,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 +360,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..198f7d542d36 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,7 @@ import static com.google.common.collect.BoundType.OPEN; import static com.google.common.collect.testing.Helpers.mapEntry; +import static org.junit.Assert.assertThrows; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.MapTestSuiteBuilder; @@ -24,6 +25,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.testing.EqualsTester; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -31,6 +33,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code TreeRangeMap}. @@ -38,6 +41,7 @@ * @author Louis Wasserman */ @GwtIncompatible // NavigableMap +@NullUnmarked public class TreeRangeMapTest extends TestCase { public static Test suite() { TestSuite suite = new TestSuite(); @@ -69,7 +73,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 +85,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 +129,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 +141,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 +184,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 +199,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 +243,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 +258,7 @@ public Iterable, String>> order( @SuppressWarnings("unchecked") @Override public Range[] createKeyArray(int length) { - return new Range[length]; + return (Range[]) new Range[length]; } @Override @@ -617,14 +621,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 +668,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 +705,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..892c988b7991 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,13 @@ 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.List; import java.util.NavigableMap; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link TreeRangeSet}. @@ -31,6 +32,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 @@ -667,14 +669,14 @@ public void testRangeContaining2() { 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..3b1b45cf2146 100644 --- a/android/guava-tests/test/com/google/common/collect/TreeTraverserTest.java +++ b/android/guava-tests/test/com/google/common/collect/TreeTraverserTest.java @@ -15,14 +15,16 @@ 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}. @@ -30,6 +32,7 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@NullMarked public class TreeTraverserTest extends TestCase { private static class Node { final char value; @@ -44,7 +47,7 @@ private static final class Tree extends Node { public Tree(char value, Tree... children) { super(value); - this.children = Arrays.asList(children); + this.children = asList(children); } } @@ -105,6 +108,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..452e513a9430 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,8 +30,10 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public class UnmodifiableIteratorTest extends TestCase { + @SuppressWarnings("DoNotCall") public void testRemove() { final String[] array = {"a", "b", "c"}; @@ -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..e1d159ae73cc 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,11 +61,7 @@ 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() { 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..61c40de4a87c --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/WriteReplaceOverridesTest.java @@ -0,0 +1,128 @@ +/* + * 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 (info.getName().endsWith("GwtSerializationDependencies")) { + continue; // These classes exist only for the GWT compiler, not to be used. + } + 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..85c754c75892 100644 --- a/android/guava-tests/test/com/google/common/escape/ArrayBasedCharEscaperTest.java +++ b/android/guava-tests/test/com/google/common/escape/ArrayBasedCharEscaperTest.java @@ -21,9 +21,13 @@ 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 = 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..a327f549bed6 100644 --- a/android/guava-tests/test/com/google/common/escape/ArrayBasedEscaperMapTest.java +++ b/android/guava-tests/test/com/google/common/escape/ArrayBasedEscaperMapTest.java @@ -16,21 +16,22 @@ package com.google.common.escape; +import static com.google.common.escape.ReflectionFreeAssertThrows.assertThrows; + 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() { @@ -62,7 +63,7 @@ public void testMapping() { char[][] replacementArray = fem.getReplacementArray(); // Array length is highest character value + 1 assertEquals(65536, replacementArray.length); - // The final element should always be non null. + // The final element should always be non-null. assertNotNull(replacementArray[replacementArray.length - 1]); // Exhaustively check all mappings (an int index avoids wrapping). for (int n = 0; n < replacementArray.length; ++n) { 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..c7ad03ad4193 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,20 @@ package com.google.common.escape; +import static com.google.common.escape.ReflectionFreeAssertThrows.assertThrows; + 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 = @@ -53,12 +59,7 @@ protected char[] escapeUnsafe(int c) { // 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 { 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..5f0ebea616ea 100644 --- a/android/guava-tests/test/com/google/common/escape/CharEscaperBuilderTest.java +++ b/android/guava-tests/test/com/google/common/escape/CharEscaperBuilderTest.java @@ -17,7 +17,9 @@ package com.google.common.escape; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +@NullUnmarked public class CharEscaperBuilderTest extends TestCase { public void testAddEscapes() { 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..650203e548fc 100644 --- a/android/guava-tests/test/com/google/common/escape/EscapersTest.java +++ b/android/guava-tests/test/com/google/common/escape/EscapersTest.java @@ -16,14 +16,19 @@ package com.google.common.escape; + 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(); @@ -60,7 +65,7 @@ public void testBuilderRetainsState() { } 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"); @@ -78,37 +83,7 @@ public void testBuilderCreatesIndependentEscapers() { 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 - } - } - - // A trival non-optimized escaper for testing. + // A trivial non-optimized escaper for testing. static CharEscaper createSimpleCharEscaper(final ImmutableMap replacementMap) { return new CharEscaper() { @Override @@ -118,7 +93,7 @@ protected char[] escape(char c) { }; } - // A trival non-optimized escaper for testing. + // A trivial non-optimized escaper for testing. static UnicodeEscaper createSimpleUnicodeEscaper( final ImmutableMap replacementMap) { return new UnicodeEscaper() { 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..7e9747348019 --- /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(emulated = true) +@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..80d6d07498d8 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,12 @@ package com.google.common.escape; +import static com.google.common.escape.ReflectionFreeAssertThrows.assertThrows; + 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 +29,7 @@ * @author David Beaumont */ @GwtCompatible +@NullUnmarked public class UnicodeEscaperTest extends TestCase { private static final String SMALLEST_SURROGATE = @@ -39,7 +44,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 +53,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(); @@ -112,28 +117,13 @@ public void testSurrogatePairs() { 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 +139,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 +148,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) { @@ -178,12 +164,9 @@ protected int nextEscapeIndex(CharSequence csq, int index, int end) { assertEquals("\0HELLO \uD800\uDC00 WORLD!\n", e.escape("\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..ec84a0e368da 100644 --- a/android/guava-tests/test/com/google/common/eventbus/AsyncEventBusTest.java +++ b/android/guava-tests/test/com/google/common/eventbus/AsyncEventBusTest.java @@ -20,12 +20,14 @@ 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"; 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..58c12be8b901 100644 --- a/android/guava-tests/test/com/google/common/eventbus/DispatcherTest.java +++ b/android/guava-tests/test/com/google/common/eventbus/DispatcherTest.java @@ -25,13 +25,14 @@ 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(); 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..e81efa36bebb 100644 --- a/android/guava-tests/test/com/google/common/eventbus/EventBusTest.java +++ b/android/guava-tests/test/com/google/common/eventbus/EventBusTest.java @@ -16,6 +16,8 @@ package com.google.common.eventbus; +import static org.junit.Assert.assertThrows; + import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import java.util.List; @@ -24,12 +26,14 @@ 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"; @@ -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); @@ -119,7 +123,7 @@ 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!"); + new RuntimeException("but culottes have a tendency to ride up!"); final Object subscriber = new Object() { @Subscribe @@ -157,18 +161,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,12 +194,7 @@ 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); @@ -222,12 +217,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,7 +229,6 @@ 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(); @@ -294,11 +283,7 @@ 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. */ 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..6ae77e5b15ab 100644 --- a/android/guava-tests/test/com/google/common/eventbus/ReentrantEventsTest.java +++ b/android/guava-tests/test/com/google/common/eventbus/ReentrantEventsTest.java @@ -19,12 +19,14 @@ import com.google.common.collect.Lists; 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"; 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..1f30e2384427 100644 --- a/android/guava-tests/test/com/google/common/eventbus/StringCatcher.java +++ b/android/guava-tests/test/com/google/common/eventbus/StringCatcher.java @@ -19,7 +19,8 @@ import com.google.common.collect.Lists; 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(); @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..a391fcbad687 --- /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.collect.Lists; +import com.google.common.eventbus.Subscribe; +import com.google.common.eventbus.outside.AbstractNotAnnotatedInSuperclassTest.SubClass; +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 = 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(); + } +} 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..aea17c3b41e7 --- /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.collect.Lists; +import com.google.common.eventbus.Subscribe; +import com.google.common.eventbus.outside.AnnotatedAndAbstractInSuperclassTest.SubClass; +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 = 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(); + } +} 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..3ec8ea4709f9 --- /dev/null +++ b/android/guava-tests/test/com/google/common/eventbus/outside/AnnotatedNotAbstractInSuperclassTest.java @@ -0,0 +1,116 @@ +/* + * 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.Subscribe; +import com.google.common.eventbus.outside.AnnotatedNotAbstractInSuperclassTest.SubClass; +import java.util.List; + +public 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(); + } +} 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..461fb795b07d --- /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.collect.Lists; +import com.google.common.eventbus.Subscribe; +import com.google.common.eventbus.outside.BaseSubscriberFinderTest.Subscriber; +import java.util.List; + +public 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(); + } +} 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..4fefbc12ab15 --- /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.collect.Lists; +import com.google.common.eventbus.Subscribe; +import com.google.common.eventbus.outside.DeepInterfaceTest.SubscriberClass; +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 = 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/NeitherAbstractNorAnnotatedInSuperclassTest.java b/android/guava-tests/test/com/google/common/eventbus/outside/NeitherAbstractNorAnnotatedInSuperclassTest.java new file mode 100644 index 000000000000..a2aca555b499 --- /dev/null +++ b/android/guava-tests/test/com/google/common/eventbus/outside/NeitherAbstractNorAnnotatedInSuperclassTest.java @@ -0,0 +1,74 @@ +/* + * 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.Subscribe; +import com.google.common.eventbus.outside.NeitherAbstractNorAnnotatedInSuperclassTest.SubClass; +import java.util.List; + +public 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(); + } +} 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..bcf9cf6d7680 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; @@ -235,12 +237,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 +249,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 +261,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 +273,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 +292,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 +304,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 +316,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 +347,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 +394,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..15a9a989a746 100644 --- a/android/guava-tests/test/com/google/common/graph/AbstractNetworkTest.java +++ b/android/guava-tests/test/com/google/common/graph/AbstractNetworkTest.java @@ -16,15 +16,17 @@ 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.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -36,6 +38,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 +60,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; @@ -415,12 +420,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 +433,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 +453,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 +476,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 +504,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 @@ -593,12 +578,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 +590,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 +602,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,12 +615,8 @@ 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 @@ -678,6 +648,28 @@ public void removeNode_existingNode() { 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 @@ -691,20 +683,52 @@ public void removeNode_nodeNotPresent() { } @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 @@ -749,12 +773,9 @@ public void removeEdge_queryAfterRemoval() { 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); - } + assertEdgeNotInGraphErrorMessage( + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.incidentNodes(E12))); } @Test @@ -785,7 +806,6 @@ public void removeEdge_parallelSelfLoopEdge() { assertThat(network.edgesConnecting(N1, N2)).containsExactly(E12); } - @Test public void concurrentIteration() throws Exception { addEdge(1, 2, "foo"); @@ -799,9 +819,9 @@ public void concurrentIteration() throws Exception { 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 +855,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..2e650a1fa378 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,17 @@ 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.assertThrows; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; 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 +38,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 +50,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 +62,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 +74,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 +86,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 @@ -364,12 +347,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); } /** @@ -398,12 +378,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 +426,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..eec759a2c31c 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,21 @@ 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.assertThrows; import static org.junit.Assert.assertTrue; 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 +67,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 +78,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 +90,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 +102,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 +130,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 +142,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 +154,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 +166,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 +178,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 +195,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 +273,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 +288,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 +483,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 +497,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 @@ -561,12 +519,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 +530,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); } /** @@ -632,25 +586,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 +608,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 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..d0f8d38163da 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,19 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; 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 +49,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 +61,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 +73,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 +85,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 +97,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 +150,8 @@ 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 @@ -380,12 +364,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..8adbaa40c360 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,17 @@ 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.assertThrows; import static org.junit.Assert.assertTrue; 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 +34,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 +62,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 +81,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 +91,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 +115,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 +125,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 +135,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 +145,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 +155,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 +398,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 +410,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 @@ -458,7 +438,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 +449,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); } /** @@ -523,25 +504,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 +526,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 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..a304ed7b8c43 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 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..20199ee8feb3 100644 --- a/android/guava-tests/test/com/google/common/graph/GraphsTest.java +++ b/android/guava-tests/test/com/google/common/graph/GraphsTest.java @@ -22,10 +22,11 @@ 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,10 +58,6 @@ 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 @@ -399,20 +397,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 +467,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 +486,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 +538,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,12 +564,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); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> NetworkBuilder.directed().expectedEdgeCount(-1)); + assertThat(e).hasMessageThat().contains(ERROR_NEGATIVE_COUNT); } private static void checkTransitiveClosure(Graph originalGraph, Graph expectedClosure) { 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..9d4889fd2634 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 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..43f48d91c986 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 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..5694d7f69ad8 100644 --- a/android/guava-tests/test/com/google/common/graph/TraverserTest.java +++ b/android/guava-tests/test/com/google/common/graph/TraverserTest.java @@ -21,7 +21,7 @@ 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; @@ -30,11 +30,13 @@ 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 { /** @@ -311,11 +313,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 +326,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 +507,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 +685,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 +739,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 +756,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 +773,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 +867,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 +1000,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 +1126,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 +1167,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); } /** 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..effd9a494735 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"; @@ -173,8 +176,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 +199,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 +229,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 +255,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 +316,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 +407,6 @@ public void incidentEdges_stableIncidentEdgeOrder_preservesIncidentEdgesOrder_un .inOrder(); } - @Test public void concurrentIteration() throws Exception { graph = ValueGraphBuilder.directed().build(); @@ -395,9 +421,9 @@ public void concurrentIteration() throws Exception { 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..2a93936a90ef 100644 --- a/android/guava-tests/test/com/google/common/hash/AbstractStreamingHasherTest.java +++ b/android/guava-tests/test/com/google/common/hash/AbstractStreamingHasherTest.java @@ -16,7 +16,8 @@ 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.assertThrows; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -29,12 +30,14 @@ 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 +116,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)); } /** 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..22453f3e6cc6 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,10 @@ 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.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableSet; @@ -35,15 +37,16 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; -import java.util.concurrent.TimeUnit; 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 +124,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 +168,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 +211,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 +224,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,15 +257,15 @@ 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. */ @@ -289,15 +284,16 @@ public void testOptimalSize() { // 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 @@ -329,7 +325,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,7 +404,7 @@ public void funnel(Long value, PrimitiveSink into) { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return (object instanceof CustomFunnel); } @@ -456,29 +452,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 +487,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,7 +502,10 @@ 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); } /** @@ -560,7 +559,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); } }; 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..2d63f46b20ca 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; } @@ -167,7 +169,7 @@ public void testCrc32cByteTable() { 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..4f6987f645fe 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 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..a764a0af527f --- /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); + final 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..46c6ac4b7f0e 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,16 @@ package com.google.common.hash; import static com.google.common.io.BaseEncoding.base16; +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 +34,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 +185,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 +195,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 +217,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,42 +233,22 @@ 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() { @@ -315,20 +299,12 @@ public void testWriteBytesToOversizedArrayShortMaxLength() { 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() { @@ -391,7 +367,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..fde53a30d6a1 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()), 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..85096df34df9 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()); @@ -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); } } } @@ -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..a9d4433b3ea8 100644 --- a/android/guava-tests/test/com/google/common/hash/HashingTest.java +++ b/android/guava-tests/test/com/google/common/hash/HashingTest.java @@ -16,10 +16,12 @@ package com.google.common.hash; -import static com.google.common.base.Charsets.UTF_8; +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; @@ -35,16 +37,18 @@ 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,6 +129,15 @@ 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) { @@ -146,7 +159,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 +168,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)); @@ -210,11 +223,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 +260,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() { @@ -296,20 +304,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() { @@ -414,30 +421,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 +458,7 @@ public void testHashIntVsForLoop() { "ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c") .put( Hashing.sha384(), - EMPTY_STRING, + "", "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da2" + "74edebfe76f65fbd51ad2f14898b95b") .put( @@ -464,7 +473,7 @@ public void testHashIntVsForLoop() { + "a7af2819a021c2fc34e91bdb63409d7") .put( Hashing.sha512(), - EMPTY_STRING, + "", "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce" + "47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e") .put( @@ -477,28 +486,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 +574,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 +588,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/AppendableWriterTest.java b/android/guava-tests/test/com/google/common/io/AppendableWriterTest.java index dd0408362bcd..7784e2d6b4bc 100644 --- a/android/guava-tests/test/com/google/common/io/AppendableWriterTest.java +++ b/android/guava-tests/test/com/google/common/io/AppendableWriterTest.java @@ -16,16 +16,20 @@ 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. */ @@ -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..1fb945643cd7 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,7 +37,8 @@ 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}. @@ -44,6 +46,7 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@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..ebbaf5c1080f 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( 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..580e3ae0c3f7 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.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. @@ -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); @@ -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..4404ada69f6a 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( @@ -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..9c0048f0ca5d 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 { @@ -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()); @@ -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); @@ -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..fb1fbd59b81e 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); 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..165f27f887e3 100644 --- a/android/guava-tests/test/com/google/common/io/CharSourceTest.java +++ b/android/guava-tests/test/com/google/common/io/CharSourceTest.java @@ -16,16 +16,18 @@ 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; @@ -33,13 +35,16 @@ import java.io.Writer; 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(); @@ -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..7d72a818f49c 100644 --- a/android/guava-tests/test/com/google/common/io/CharSourceTester.java +++ b/android/guava-tests/test/com/google/common/io/CharSourceTester.java @@ -16,7 +16,8 @@ package com.google.common.io; -import com.google.common.base.Charsets; +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; @@ -30,15 +31,17 @@ 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 +51,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 +61,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( 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..023542225976 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,8 @@ package com.google.common.io; +import static org.junit.Assert.assertThrows; + import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import java.io.EOFException; @@ -27,12 +29,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."; @@ -116,13 +120,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 { @@ -218,7 +218,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 +226,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 +271,9 @@ public void testNullWriter() throws Exception { nullWriter.append(null); nullWriter.append(null, 0, 4); - try { - nullWriter.append(null, -1, 4); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - - try { - nullWriter.append(null, 0, 5); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> nullWriter.append(null, -1, 4)); + + assertThrows(IndexOutOfBoundsException.class, () -> nullWriter.append(null, 0, 5)); // nothing really to assert? assertSame(CharStreams.nullWriter(), CharStreams.nullWriter()); 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..1181b9c46740 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.List; -import java.util.logging.LogRecord; 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); @@ -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,6 +320,7 @@ 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 { @@ -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) @@ -435,7 +387,7 @@ static TestCloseable throwsOnCreate() throws IOException { throw new IOException(); } - private TestCloseable(@NullableDecl Throwable throwOnClose) { + private TestCloseable(@Nullable Throwable throwOnClose) { this.throwOnClose = throwOnClose; } @@ -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..1386de3c45b6 --- /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); + 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(); + } + }); + } +} 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..3cbf5a1028a7 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,30 @@ package com.google.common.io; +import static com.google.common.base.StandardSystemProperty.JAVA_IO_TMPDIR; +import static com.google.common.base.StandardSystemProperty.OS_NAME; +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.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFileAttributes; import java.util.Arrays; +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 +60,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 @@ -61,10 +73,21 @@ private void testThreshold( // Write data to go over the threshold if (chunk2 > 0) { + if (JAVA_IO_TMPDIR.value().equals("/sdcard")) { + assertThrows(IOException.class, () -> write(out, data, chunk1, chunk2, singleByte)); + return; + } write(out, data, chunk1, chunk2, singleByte); 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(); @@ -79,30 +102,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 +113,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++) { @@ -133,15 +132,15 @@ public void testWriteErrorAfterClose() throws Exception { FileBackedOutputStream out = new FileBackedOutputStream(50); ByteSource source = out.asByteSource(); + if (JAVA_IO_TMPDIR.value().equals("/sdcard")) { + assertThrows(IOException.class, () -> out.write(data)); + return; + } out.write(data); assertTrue(Arrays.equals(data, source.read())); 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())); @@ -164,4 +163,12 @@ public void testReset() throws Exception { out.close(); } + + private static boolean isAndroid() { + return System.getProperty("java.runtime.name", "").contains("Android"); + } + + private static boolean isWindows() { + return OS_NAME.value().startsWith("Windows"); + } } 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..1b15f3c8569b --- /dev/null +++ b/android/guava-tests/test/com/google/common/io/FilesCreateTempDirTest.java @@ -0,0 +1,119 @@ +/* + * 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.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 static org.junit.Assert.assertThrows; + +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 { + if (JAVA_IO_TMPDIR.value().equals("/sdcard")) { + assertThrows(IllegalStateException.class, Files::createTempDir); + return; + } + 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..1111471e185e 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; @@ -38,17 +41,26 @@ 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 +90,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())); + assertTrue(Arrays.equals(ASCII.getBytes(US_ASCII), Files.toByteArray(asciiFile))); + assertTrue(Arrays.equals(I18N.getBytes(UTF_8), Files.toByteArray(i18nFile))); + assertTrue(Arrays.equals(I18N.getBytes(UTF_8), Files.asByteSource(i18nFile).read())); } /** A {@link File} that provides a specialized value for {@link File#length()}. */ @@ -110,15 +122,15 @@ 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 { @@ -127,21 +139,17 @@ public void testWriteBytes() throws IOException { Files.write(data, temp); assertTrue(Arrays.equals(data, Files.toByteArray(temp))); - 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 +162,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 +170,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 +226,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 +240,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 +265,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 { @@ -350,19 +333,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 +364,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 { @@ -443,19 +410,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()); + assertNull(Files.readFirstLine(temp, UTF_8)); + 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()); } @@ -477,15 +443,15 @@ 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 = @@ -505,7 +471,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 +515,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 { @@ -605,11 +567,9 @@ 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() { 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..ddb8bdeace43 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; 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..cc1ba24095ae 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; @@ -26,6 +30,7 @@ import java.nio.CharBuffer; import java.util.Arrays; import java.util.List; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link LineBuffer} and {@link LineReader}. @@ -33,6 +38,7 @@ * @author Chris Nokleberg */ @AndroidIncompatible // occasionally very slow +@NullUnmarked public class LineBufferTest extends IoTestCase { public void testProcess() throws IOException { @@ -53,7 +59,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 +76,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)); @@ -89,7 +96,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; } @@ -136,7 +143,7 @@ private static Reader getChunkedReader(String input, final 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..a1643a62a8b2 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,12 +25,14 @@ 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(); @@ -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..8028759ff971 --- /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 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.Executors; +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(); + assertNull(root.getParent()); + assertNull(root.toRealPath().getParent()); + MoreFiles.createParentDirectories(root); // test that there's no exception + } + } + + public void testCreateParentDirectories_relativePath() throws IOException { + Path path = FS.getPath("nonexistent.file"); + assertNull(path.getParent()); + assertNotNull(path.toAbsolutePath().getParent()); + 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 = Executors.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( + final Path file, final 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)); + } + }; + + public abstract void delete(Path path, RecursiveDeleteOption... options) throws IOException; + + public 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..d9e30620ea62 100644 --- a/android/guava-tests/test/com/google/common/io/MultiInputStreamTest.java +++ b/android/guava-tests/test/com/google/common/io/MultiInputStreamTest.java @@ -23,12 +23,14 @@ import java.io.InputStream; 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 { 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..0b335d9da3ac 100644 --- a/android/guava-tests/test/com/google/common/io/MultiReaderTest.java +++ b/android/guava-tests/test/com/google/common/io/MultiReaderTest.java @@ -22,8 +22,12 @@ 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 { 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..b428136b0265 --- /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(emulated = true) +@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..1e8a3026d7c1 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,19 +61,19 @@ 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 { @@ -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,12 +107,10 @@ 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() { @@ -118,16 +118,15 @@ public void testGetResource() { } 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() { @@ -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); } @@ -171,16 +166,13 @@ public void testGetResource_contextClassLoaderNull() { 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) { - } + 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..1fdcc8c13d21 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() { @@ -103,12 +106,12 @@ public static ByteSourceFactory asByteSourceFactory(final CharSourceFactory fact 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 @@ -123,12 +126,12 @@ public static CharSourceFactory asCharSourceFactory(final ByteSourceFactory fact 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 @@ -143,12 +146,12 @@ public static CharSinkFactory asCharSinkFactory(final ByteSinkFactory 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 @@ -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); } @@ -305,7 +308,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 +359,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 +378,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 +386,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 +406,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 +434,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..9d086c0d4ae3 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 { /** 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..28bf66f853d3 100644 --- a/android/guava-tests/test/com/google/common/io/SourceSinkTester.java +++ b/android/guava-tests/test/com/google/common/io/SourceSinkTester.java @@ -28,6 +28,7 @@ import java.lang.reflect.Modifier; 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; 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..756fdd133d9f 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(); 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..1f068c423b23 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; 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/BigDecimalMathTest.java b/android/guava-tests/test/com/google/common/math/BigDecimalMathTest.java index ff86fd52f5f2..ba78c5dba7cc 100644 --- a/android/guava-tests/test/com/google/common/math/BigDecimalMathTest.java +++ b/android/guava-tests/test/com/google/common/math/BigDecimalMathTest.java @@ -25,6 +25,7 @@ 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 java.math.BigDecimal; @@ -34,8 +35,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; @@ -76,12 +79,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 +181,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 +249,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..f710ecdaa683 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,12 +52,14 @@ import java.util.EnumSet; import java.util.Map; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Tests for BigIntegerMath. * * @author Louis Wasserman */ +@NullMarked @GwtCompatible(emulated = true) public class BigIntegerMathTest extends TestCase { public void testCeilingPowerOfTwo() { @@ -76,38 +82,24 @@ public void testFloorPowerOfTwo() { 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)); } } @@ -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)); } } @@ -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)); } } @@ -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++) { + final 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,6 +524,7 @@ RoundToDoubleTester setExpectation(double expectedValue, RoundingMode... modes) return this; } + @CanIgnoreReturnValue public RoundToDoubleTester roundUnnecessaryShouldThrow() { unnecessaryShouldThrow = true; return this; @@ -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..27f9f481e1b4 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,8 +51,8 @@ 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}. @@ -57,6 +60,7 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@NullUnmarked public class DoubleMathTest extends TestCase { private static final BigDecimal MAX_INT_AS_BIG_DECIMAL = BigDecimal.valueOf(Integer.MAX_VALUE); @@ -74,8 +78,8 @@ public void testConstantsMaxFactorial() { 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..38044cd4a6db 100644 --- a/android/guava-tests/test/com/google/common/math/IntMathTest.java +++ b/android/guava-tests/test/com/google/common/math/IntMathTest.java @@ -23,19 +23,23 @@ 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.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}. @@ -43,6 +47,7 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@NullUnmarked public class IntMathTest extends TestCase { public void testMaxSignedPowerOfTwo() { assertTrue(IntMath.isPowerOfTwo(IntMath.MAX_SIGNED_POWER_OF_TWO)); @@ -58,11 +63,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 +77,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 +122,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 +148,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 +177,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 +215,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 +223,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 +274,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)); } } } @@ -399,11 +365,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 +381,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 +410,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 +566,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 +582,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++) { + final 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) { - } + for (final int n : NEGATIVE_INTEGER_CANDIDATES) { + assertThrows(IllegalArgumentException.class, () -> IntMath.binomial(n, 0)); } } @@ -744,6 +667,7 @@ private static boolean fitsInInt(BigInteger big) { return big.bitLength() <= 31; } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); 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..e15ee30c5b18 100644 --- a/android/guava-tests/test/com/google/common/math/LongMathTest.java +++ b/android/guava-tests/test/com/google/common/math/LongMathTest.java @@ -25,6 +25,7 @@ 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; @@ -33,6 +34,7 @@ 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,6 +42,7 @@ import java.util.EnumSet; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for LongMath. @@ -47,6 +50,7 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@NullUnmarked public class LongMathTest extends TestCase { @SuppressWarnings("ConstantOverflow") public void testMaxSignedPowerOfTwo() { @@ -60,11 +64,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 +78,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 +118,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 @@ -156,8 +137,8 @@ public void testConstantsHalfPowersOf10() { @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 +147,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 +171,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++) { + final 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 +223,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 +262,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 +270,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 +314,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)); } } } @@ -402,6 +356,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() { @@ -452,11 +407,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 +425,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 +433,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 +451,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 +476,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 +508,6 @@ public void testCheckedAdd() { } } - @GwtIncompatible // TODO @AndroidIncompatible // slow public void testCheckedSubtract() { for (long a : ALL_LONG_CANDIDATES) { @@ -728,11 +649,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,31 +677,21 @@ 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++) { + final 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) { - } + for (final int n : NEGATIVE_INTEGER_CANDIDATES) { + 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. @@ -884,6 +791,7 @@ private static long saturatedCast(BigInteger big) { return big.longValue(); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); @@ -946,11 +854,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 +893,7 @@ public void testIsPrimeThrowsOnNegative() { Long.MIN_VALUE }; + @J2ktIncompatible // EnumSet.complementOf @GwtIncompatible public void testRoundToDoubleAgainstBigInteger() { for (RoundingMode roundingMode : EnumSet.complementOf(EnumSet.of(UNNECESSARY))) { @@ -1012,12 +917,8 @@ 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)); } } } 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..6f46dfe6c2a8 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; 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..7e2fdc4a5737 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,6 +44,7 @@ * @author Louis Wasserman */ @GwtCompatible +@NullUnmarked public 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)); 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..28dff1009f28 --- /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(emulated = true) +@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..ba05ce7294bf 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,19 @@ package com.google.common.math; import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.NullUnmarked; -/** @author Chris Povirk */ +/** + * @author Chris Povirk + */ @GwtCompatible(emulated = true) +@NullUnmarked 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"); } } 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..58a735fc28b4 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,14 @@ package com.google.common.net; +import static com.google.common.net.ReflectionFreeAssertThrows.assertThrows; + 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 +31,7 @@ * @author Paul Marks */ @GwtCompatible +@NullUnmarked public class HostAndPortTest extends TestCase { public void testFromStringWellFormed() { @@ -59,6 +64,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 +107,7 @@ public void testFromStringParseableNonsense() { private static void checkFromStringCase( String hpString, int defaultPort, - String expectHost, + @Nullable String expectHost, int expectPort, boolean expectHasExplicitPort) { HostAndPort hp; @@ -152,17 +164,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 +178,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 +214,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..6b7ca2f39902 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 = @@ -92,8 +94,9 @@ public void testNulls() { } 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..b89202ec9347 100644 --- a/android/guava-tests/test/com/google/common/net/HttpHeadersTest.java +++ b/android/guava-tests/test/com/google/common/net/HttpHeadersTest.java @@ -25,12 +25,14 @@ import java.lang.reflect.Field; 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 +42,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,11 +50,11 @@ 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"); + "CH", "ID", "DNT", "DNS", "DPR", "ECT", "GPC", "HTTP2", "IP", "MD5", "P3P", "RTT", "TE", + "UA", "UID", "URL", "WWW", "XSS"); assertConstantNameMatchesString(HttpHeaders.class, specialCases, uppercaseAcronyms); } 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..213ebfbf5ef8 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,11 @@ package com.google.common.net; +import static com.google.common.net.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.Ascii; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; @@ -26,6 +28,7 @@ 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}. @@ -33,6 +36,7 @@ * @author Craig Berry */ @GwtCompatible(emulated = true) +@NullUnmarked public final class InternetDomainNameTest extends TestCase { private static final InternetDomainName UNICODE_EXAMPLE = InternetDomainName.from("j\u00f8rpeland.no"); @@ -43,74 +47,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", + "co.uk.", // Trailing dot + "co\uFF61uk", // Alternate dot character + "com", "foo.bd", - "xxxxxx.bd", "org.mK", "us", - "co.uk.", // Trailing dot - "co\uFF61uk", // Alternate dot character - "\u7f51\u7edc.Cn", // "网络.Cn" + "xxxxxx.bd", + // 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 +141,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 +186,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,17 +251,13 @@ 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)); } } @@ -365,11 +380,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 +388,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 +399,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,11 +414,8 @@ 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()); } } @@ -461,7 +465,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 +484,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,6 +505,7 @@ private static InternetDomainName idn(String domain) { return InternetDomainName.from(domain); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { final NullPointerTester tester = new NullPointerTester(); 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..da6c4914b0b6 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,10 +48,10 @@ 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}. @@ -57,7 +59,9 @@ * @author Gregory Kick */ @GwtCompatible(emulated = true) +@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..6f33ba1078be 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)); } /** @@ -112,31 +110,20 @@ public void testBadArguments_null() { 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); - } + 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..806c2a1b133b --- /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(emulated = true) +@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/BooleansTest.java b/android/guava-tests/test/com/google/common/primitives/BooleansTest.java index 560c33700382..e6e907062059 100644 --- a/android/guava-tests/test/com/google/common/primitives/BooleansTest.java +++ b/android/guava-tests/test/com/google/common/primitives/BooleansTest.java @@ -16,8 +16,13 @@ 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.testing.Helpers; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; @@ -27,6 +32,8 @@ 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}. @@ -34,6 +41,7 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@NullMarked public class BooleansTest extends TestCase { private static final boolean[] EMPTY = {}; private static final boolean[] ARRAY_FALSE = {false}; @@ -44,116 +52,137 @@ public class BooleansTest extends TestCase { private static final boolean[] VALUES = {false, true}; 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 +201,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 +219,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 +239,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 +497,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(Collections.emptyList())).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..12388907e8c4 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,6 +32,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Bytes#asList(byte[])}. @@ -38,6 +40,7 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@NullUnmarked public class ByteArrayAsListTest extends TestCase { private static List asList(Byte[] values) { @@ -48,6 +51,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..3aa23f5f7b2e 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,12 +29,15 @@ 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 */ +@NullMarked @GwtCompatible(emulated = true) public class BytesTest extends TestCase { private static final byte[] EMPTY = {}; @@ -41,127 +48,149 @@ public class BytesTest extends TestCase { public void testHashCode() { for (byte value : VALUES) { - assertEquals(((Byte) value).hashCode(), Bytes.hashCode(value)); + assertThat(Bytes.hashCode(value)).isEqualTo(((Byte) value).hashCode()); } } 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 +200,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() { @@ -198,21 +223,22 @@ public void testToArray_withConversion() { 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))); + 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 +248,21 @@ 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[] {}); } public void testAsListEmpty() { - assertSame(Collections.emptyList(), Bytes.asList(EMPTY)); + assertThat(Bytes.asList(EMPTY)).isSameInstanceAs(Collections.emptyList()); } public void testReverse() { @@ -250,13 +276,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 +294,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..162d03358dfc 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,6 +32,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Chars#asList(char[])}. @@ -38,6 +40,7 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@NullUnmarked public class CharArrayAsListTest extends TestCase { private static List asList(Character[] values) { @@ -48,6 +51,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..30e1c3990395 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,6 +35,8 @@ 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}. @@ -35,7 +44,7 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) -@SuppressWarnings("cast") // redundant casts are intentional and harmless +@NullMarked public class CharsTest extends TestCase { private static final char[] EMPTY = {}; private static final char[] ARRAY1 = {(char) 1}; @@ -48,13 +57,13 @@ public class CharsTest extends TestCase { public void testHashCode() { for (char value : VALUES) { - assertEquals(((Character) value).hashCode(), Chars.hashCode(value)); + assertThat(Chars.hashCode(value)).isEqualTo(((Character) value).hashCode()); } } 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 +73,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 +86,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.valueOf(x).compareTo(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 +272,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 +335,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 +353,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 +371,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 +579,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 +601,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 +621,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 +651,24 @@ 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[] {}); } 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..7515073fce9b 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,6 +32,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Doubles#asList(double[])}. @@ -38,6 +40,7 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@NullUnmarked public class DoubleArrayAsListTest extends TestCase { private static List asList(Double[] values) { @@ -48,12 +51,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 +88,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}; 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..e0bb4a8736b3 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,14 +38,16 @@ 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 */ +@NullMarked @GwtCompatible(emulated = true) -@SuppressWarnings("cast") // redundant casts are intentional and harmless public class DoublesTest extends TestCase { private static final double[] EMPTY = {}; private static final double[] ARRAY1 = {(double) 1}; @@ -77,242 +84,256 @@ public class DoublesTest extends TestCase { public void testHashCode() { for (double value : VALUES) { - assertEquals(((Double) value).hashCode(), Doubles.hashCode(value)); + assertThat(Doubles.hashCode(value)).isEqualTo(((Double) value).hashCode()); } } public void testIsFinite() { for (double value : NUMBERS) { - assertEquals(!(Double.isNaN(value) || Double.isInfinite(value)), Doubles.isFinite(value)); + assertThat(Doubles.isFinite(value)) + .isEqualTo(!(Double.isNaN(value) || Double.isInfinite(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.valueOf(x).compareTo(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, (double) 1)).isFalse(); + assertThat(Doubles.contains(ARRAY1, (double) 2)).isFalse(); + assertThat(Doubles.contains(ARRAY234, (double) 1)).isFalse(); + assertThat(Doubles.contains(new double[] {(double) -1}, (double) -1)).isTrue(); + assertThat(Doubles.contains(ARRAY234, (double) 2)).isTrue(); + assertThat(Doubles.contains(ARRAY234, (double) 3)).isTrue(); + assertThat(Doubles.contains(ARRAY234, (double) 4)).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, (double) 1)).isEqualTo(-1); + assertThat(Doubles.indexOf(ARRAY1, (double) 2)).isEqualTo(-1); + assertThat(Doubles.indexOf(ARRAY234, (double) 1)).isEqualTo(-1); + assertThat(Doubles.indexOf(new double[] {(double) -1}, (double) -1)).isEqualTo(0); + assertThat(Doubles.indexOf(ARRAY234, (double) 2)).isEqualTo(0); + assertThat(Doubles.indexOf(ARRAY234, (double) 3)).isEqualTo(1); + assertThat(Doubles.indexOf(ARRAY234, (double) 4)).isEqualTo(2); + assertThat( + Doubles.indexOf( + new double[] {(double) 2, (double) 3, (double) 2, (double) 3}, (double) 3)) + .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[] {(double) 2, (double) 3})).isEqualTo(0); + assertThat(Doubles.indexOf(ARRAY234, new double[] {(double) 3, (double) 4})).isEqualTo(1); + assertThat(Doubles.indexOf(ARRAY234, new double[] {(double) 3})).isEqualTo(1); + assertThat(Doubles.indexOf(ARRAY234, new double[] {(double) 4})).isEqualTo(2); + assertThat( + Doubles.indexOf( + new double[] {(double) 2, (double) 3, (double) 3, (double) 3, (double) 3}, + new double[] {(double) 3})) + .isEqualTo(1); + assertThat( + 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})) + .isEqualTo(2); + assertThat( + 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})) + .isEqualTo(1); + assertThat( + Doubles.indexOf( + new double[] {(double) 4, (double) 3, (double) 2}, + new double[] {(double) 2, (double) 3, (double) 4})) + .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, (double) 1)).isEqualTo(-1); + assertThat(Doubles.lastIndexOf(ARRAY1, (double) 2)).isEqualTo(-1); + assertThat(Doubles.lastIndexOf(ARRAY234, (double) 1)).isEqualTo(-1); + assertThat(Doubles.lastIndexOf(new double[] {(double) -1}, (double) -1)).isEqualTo(0); + assertThat(Doubles.lastIndexOf(ARRAY234, (double) 2)).isEqualTo(0); + assertThat(Doubles.lastIndexOf(ARRAY234, (double) 3)).isEqualTo(1); + assertThat(Doubles.lastIndexOf(ARRAY234, (double) 4)).isEqualTo(2); + assertThat( + Doubles.lastIndexOf( + new double[] {(double) 2, (double) 3, (double) 2, (double) 3}, (double) 3)) + .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((double) 8, (double) 6, (double) 7, (double) 5, (double) 3, (double) 0, (double) 9)) + .isEqualTo((double) 9); - 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((double) 8, (double) 6, (double) 7, (double) 5, (double) 3, (double) 0, (double) 9)) + .isEqualTo((double) 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); + assertThat(Doubles.constrainToRange((double) 1, (double) 0, (double) 5)).isEqualTo((double) 1); + assertThat(Doubles.constrainToRange((double) 1, (double) 1, (double) 5)).isEqualTo((double) 1); + assertThat(Doubles.constrainToRange((double) 1, (double) 3, (double) 5)).isEqualTo((double) 3); + assertThat(Doubles.constrainToRange((double) 0, (double) -5, (double) -1)) + .isEqualTo((double) -1); + assertThat(Doubles.constrainToRange((double) 5, (double) 2, (double) 2)).isEqualTo((double) 2); + assertThrows( + IllegalArgumentException.class, + () -> Doubles.constrainToRange((double) 1, (double) 3, (double) 2)); + } + + public void testConcat() { + 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[] {(double) 1, (double) 1, (double) 1}); + assertThat(Doubles.concat(ARRAY1, ARRAY234)) + .isEqualTo(new double[] {(double) 1, (double) 2, (double) 3, (double) 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)); + + 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 { - Doubles.constrainToRange((double) 1, (double) 3, (double) 2); + Doubles.concat(arrays); fail(); } catch (IllegalArgumentException expected) { } } - 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))); - } - 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))); + 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[] {(double) 1, (double) 0, (double) 0}, + Doubles.ensureCapacity(ARRAY1, 2, 1))) + .isTrue(); } public void testEnsureCapacity_fail() { - try { - Doubles.ensureCapacity(ARRAY1, -1, 1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - // notice that this should even fail when no growth was needed - Doubles.ensureCapacity(ARRAY1, 1, -1); - fail(); - } catch (IllegalArgumentException expected) { - } + 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(",", (double) 1, (double) 2)).isEqualTo("1.0,2.0"); + assertThat(Doubles.join("", (double) 1, (double) 2, (double) 3)).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() { @@ -343,14 +364,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 +383,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 +487,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 +503,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 +519,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 +535,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))); + assertThat(Doubles.toArray(one)).isEqualTo(ARRAY1); double[] array = {(double) 0, (double) 1, Math.PI}; List three = Arrays.asList((double) 0, (double) 1, Math.PI); - assertTrue(Arrays.equals(array, Doubles.toArray(three))); + 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,21 +555,17 @@ 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((double) 0, (double) 1, null); + assertThrows(NullPointerException.class, () -> Doubles.toArray(list)); } public void testToArray_withConversion() { @@ -464,19 +578,20 @@ public void testToArray_withConversion() { List longs = Arrays.asList((long) 0, (long) 1, (long) 2); List doubles = Arrays.asList((double) 0, (double) 1, (double) 2); - 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}; List list = Doubles.asList(array); list.set(0, (double) 2); - assertTrue(Arrays.equals(new double[] {(double) 2, (double) 1}, array)); + assertThat(array).isEqualTo(new double[] {(double) 2, (double) 1}); array[1] = (double) 3; assertThat(list).containsExactly((double) 2, (double) 3).inOrder(); } @@ -488,29 +603,29 @@ public void testAsList_toArray_roundTrip() { // Make sure it returned a copy list.set(0, (double) 4); - assertTrue(Arrays.equals(new double[] {(double) 0, (double) 1, (double) 2}, newArray)); + assertThat(newArray).isEqualTo(new double[] {(double) 0, (double) 1, (double) 2}); newArray[1] = (double) 5; - assertEquals((double) 1, (double) list.get(1)); + assertThat((double) list.get(1)).isEqualTo((double) 1); } // 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}; 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[] {(double) 1, (double) 2}); + assertThat(Doubles.toArray(list.subList(2, 2))).isEmpty(); } 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 +639,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 +656,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 +701,7 @@ public void testTryParseOfToStringIsOriginal() { } } + @J2ktIncompatible // hexadecimal doubles @GwtIncompatible // Doubles.tryParse public void testTryParseOfToHexStringIsOriginal() { for (double d : NUMBERS) { @@ -632,11 +748,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 +761,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((Double) 1.0); + assertThat(converter.convert("0.0")).isEqualTo((Double) 0.0); + assertThat(converter.convert("-1.0")).isEqualTo((Double) (-1.0)); + assertThat(converter.convert("1")).isEqualTo((Double) 1.0); + assertThat(converter.convert("0")).isEqualTo((Double) 0.0); + assertThat(converter.convert("-1")).isEqualTo((Double) (-1.0)); + assertThat(converter.convert("1e6")).isEqualTo((Double) 1e6); + assertThat(converter.convert("1e-6")).isEqualTo((Double) 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 +800,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..9fee9033ce08 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,6 +32,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Floats#asList(float[])})}. @@ -38,6 +40,7 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@NullUnmarked public class FloatArrayAsListTest extends TestCase { private static List asList(Float[] values) { @@ -48,6 +51,7 @@ private static List asList(Float[] values) { return Floats.asList(temp); } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { List> builders = 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..0423cf46599e 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,14 +37,16 @@ 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 */ +@NullMarked @GwtCompatible(emulated = true) -@SuppressWarnings("cast") // redundant casts are intentional and harmless public class FloatsTest extends TestCase { private static final float[] EMPTY = {}; private static final float[] ARRAY1 = {(float) 1}; @@ -72,223 +79,240 @@ public class FloatsTest extends TestCase { public void testHashCode() { for (float value : VALUES) { - assertEquals(((Float) value).hashCode(), Floats.hashCode(value)); + assertThat(Floats.hashCode(value)).isEqualTo(((Float) value).hashCode()); } } public void testIsFinite() { for (float value : NUMBERS) { - assertEquals(!(Float.isInfinite(value) || Float.isNaN(value)), Floats.isFinite(value)); + assertThat(Floats.isFinite(value)) + .isEqualTo(!(Float.isInfinite(value) || Float.isNaN(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.valueOf(x).compareTo(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, (float) 1)).isFalse(); + assertThat(Floats.contains(ARRAY1, (float) 2)).isFalse(); + assertThat(Floats.contains(ARRAY234, (float) 1)).isFalse(); + assertThat(Floats.contains(new float[] {(float) -1}, (float) -1)).isTrue(); + assertThat(Floats.contains(ARRAY234, (float) 2)).isTrue(); + assertThat(Floats.contains(ARRAY234, (float) 3)).isTrue(); + assertThat(Floats.contains(ARRAY234, (float) 4)).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, (float) 1)).isEqualTo(-1); + assertThat(Floats.indexOf(ARRAY1, (float) 2)).isEqualTo(-1); + assertThat(Floats.indexOf(ARRAY234, (float) 1)).isEqualTo(-1); + assertThat(Floats.indexOf(new float[] {(float) -1}, (float) -1)).isEqualTo(0); + assertThat(Floats.indexOf(ARRAY234, (float) 2)).isEqualTo(0); + assertThat(Floats.indexOf(ARRAY234, (float) 3)).isEqualTo(1); + assertThat(Floats.indexOf(ARRAY234, (float) 4)).isEqualTo(2); + assertThat(Floats.indexOf(new float[] {(float) 2, (float) 3, (float) 2, (float) 3}, (float) 3)) + .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[] {(float) 2, (float) 3})).isEqualTo(0); + assertThat(Floats.indexOf(ARRAY234, new float[] {(float) 3, (float) 4})).isEqualTo(1); + assertThat(Floats.indexOf(ARRAY234, new float[] {(float) 3})).isEqualTo(1); + assertThat(Floats.indexOf(ARRAY234, new float[] {(float) 4})).isEqualTo(2); + assertThat( + Floats.indexOf( + new float[] {(float) 2, (float) 3, (float) 3, (float) 3, (float) 3}, + new float[] {(float) 3})) + .isEqualTo(1); + assertThat( + 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})) + .isEqualTo(2); + assertThat( + 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})) + .isEqualTo(1); + assertThat( + Floats.indexOf( + new float[] {(float) 4, (float) 3, (float) 2}, + new float[] {(float) 2, (float) 3, (float) 4})) + .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, (float) 1)).isEqualTo(-1); + assertThat(Floats.lastIndexOf(ARRAY1, (float) 2)).isEqualTo(-1); + assertThat(Floats.lastIndexOf(ARRAY234, (float) 1)).isEqualTo(-1); + assertThat(Floats.lastIndexOf(new float[] {(float) -1}, (float) -1)).isEqualTo(0); + assertThat(Floats.lastIndexOf(ARRAY234, (float) 2)).isEqualTo(0); + assertThat(Floats.lastIndexOf(ARRAY234, (float) 3)).isEqualTo(1); + assertThat(Floats.lastIndexOf(ARRAY234, (float) 4)).isEqualTo(2); + assertThat( + Floats.lastIndexOf(new float[] {(float) 2, (float) 3, (float) 2, (float) 3}, (float) 3)) + .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((float) 8, (float) 6, (float) 7, (float) 5, (float) 3, (float) 0, (float) 9)) + .isEqualTo((float) 9); - 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((float) 8, (float) 6, (float) 7, (float) 5, (float) 3, (float) 0, (float) 9)) + .isEqualTo((float) 0); - 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); + assertThat(Floats.constrainToRange((float) 1, (float) 0, (float) 5)).isEqualTo((float) 1); + assertThat(Floats.constrainToRange((float) 1, (float) 1, (float) 5)).isEqualTo((float) 1); + assertThat(Floats.constrainToRange((float) 1, (float) 3, (float) 5)).isEqualTo((float) 3); + assertThat(Floats.constrainToRange((float) 0, (float) -5, (float) -1)).isEqualTo((float) -1); + assertThat(Floats.constrainToRange((float) 5, (float) 2, (float) 2)).isEqualTo((float) 2); + assertThrows( + IllegalArgumentException.class, + () -> Floats.constrainToRange((float) 1, (float) 3, (float) 2)); + } + + public void testConcat() { + 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[] {(float) 1, (float) 1, (float) 1}); + assertThat(Floats.concat(ARRAY1, ARRAY234)) + .isEqualTo(new float[] {(float) 1, (float) 2, (float) 3, (float) 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)); + + 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 { - Floats.constrainToRange((float) 1, (float) 3, (float) 2); + Floats.concat(arrays); fail(); } catch (IllegalArgumentException expected) { } } - 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))); - } - 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))); + 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[] {(float) 1, (float) 0, (float) 0}, Floats.ensureCapacity(ARRAY1, 2, 1))) + .isTrue(); } public void testEnsureCapacity_fail() { - try { - Floats.ensureCapacity(ARRAY1, -1, 1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - // notice that this should even fail when no growth was needed - Floats.ensureCapacity(ARRAY1, 1, -1); - fail(); - } catch (IllegalArgumentException expected) { - } + 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(",", (float) 1, (float) 2)).isEqualTo("1.0,2.0"); + assertThat(Floats.join("", (float) 1, (float) 2, (float) 3)).isEqualTo("1.02.03.0"); } public void testLexicographicalComparator() { @@ -308,10 +332,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 +350,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 +369,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 +473,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 +489,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 +505,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 +514,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))); + assertThat(Floats.toArray(one)).isEqualTo(ARRAY1); float[] array = {(float) 0, (float) 1, (float) 3}; List three = Arrays.asList((float) 0, (float) 1, (float) 3); - assertTrue(Arrays.equals(array, Floats.toArray(three))); + 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,21 +534,17 @@ 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((float) 0, (float) 1, null); + assertThrows(NullPointerException.class, () -> Floats.toArray(list)); } public void testToArray_withConversion() { @@ -439,19 +557,20 @@ public void testToArray_withConversion() { List longs = Arrays.asList((long) 0, (long) 1, (long) 2); List doubles = Arrays.asList((double) 0, (double) 1, (double) 2); - 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}; List list = Floats.asList(array); list.set(0, (float) 2); - assertTrue(Arrays.equals(new float[] {(float) 2, (float) 1}, array)); + assertThat(array).isEqualTo(new float[] {(float) 2, (float) 1}); array[1] = (float) 3; assertThat(list).containsExactly((float) 2, (float) 3).inOrder(); } @@ -463,29 +582,28 @@ public void testAsList_toArray_roundTrip() { // Make sure it returned a copy list.set(0, (float) 4); - assertTrue(Arrays.equals(new float[] {(float) 0, (float) 1, (float) 2}, newArray)); + assertThat(newArray).isEqualTo(new float[] {(float) 0, (float) 1, (float) 2}); newArray[1] = (float) 5; - assertEquals((float) 1, (float) list.get(1)); + assertThat((float) list.get(1)).isEqualTo((float) 1); } // 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}; 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[] {(float) 1, (float) 2}); + assertThat(Floats.toArray(list.subList(2, 2))).isEmpty(); } 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 +616,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 +662,7 @@ public void testTryParseOfToStringIsOriginal() { } } + @J2ktIncompatible // hexadecimal floats @GwtIncompatible // Floats.tryParse public void testTryParseOfToHexStringIsOriginal() { for (float f : NUMBERS) { @@ -585,11 +704,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 +718,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((Float) 1.0f); + assertThat(converter.convert("0.0")).isEqualTo((Float) 0.0f); + assertThat(converter.convert("-1.0")).isEqualTo((Float) (-1.0f)); + assertThat(converter.convert("1")).isEqualTo((Float) 1.0f); + assertThat(converter.convert("0")).isEqualTo((Float) 0.0f); + assertThat(converter.convert("-1")).isEqualTo((Float) (-1.0f)); + assertThat(converter.convert("1e6")).isEqualTo((Float) 1e6f); + assertThat(converter.convert("1e-6")).isEqualTo((Float) 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 +757,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..a7ea0cfa968c 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 */ +/** + * @author Kevin Bourrillion + */ @GwtCompatible(emulated = true) +@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)); } /* @@ -399,6 +418,7 @@ public void testTrimmed() { assertActuallyTrims(underSized); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { assertThat(reserialize(ImmutableDoubleArray.of())).isSameInstanceAs(ImmutableDoubleArray.of()); @@ -423,6 +443,7 @@ private static void assertDoesntActuallyTrim(ImmutableDoubleArray iia) { assertThat(iia.trimmed()).isSameInstanceAs(iia); } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { List> builders = @@ -454,6 +475,7 @@ public static Test suite() { return suite; } + @J2ktIncompatible @GwtIncompatible // used only from suite private static ImmutableDoubleArray makeArray(Double[] values) { return ImmutableDoubleArray.copyOf(Arrays.asList(values)); @@ -462,6 +484,7 @@ 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 public static final class ImmutableDoubleArrayAsListGenerator extends TestDoubleListGenerator { @Override @@ -470,6 +493,7 @@ protected List create(Double[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static final class ImmutableDoubleArrayHeadSubListAsListGenerator extends TestDoubleListGenerator { @@ -481,6 +505,7 @@ protected List create(Double[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static final class ImmutableDoubleArrayTailSubListAsListGenerator extends TestDoubleListGenerator { @@ -492,6 +517,7 @@ protected List create(Double[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static final class ImmutableDoubleArrayMiddleSubListAsListGenerator extends TestDoubleListGenerator { @@ -504,11 +530,13 @@ protected List create(Double[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite private static Double[] concat(Double[] a, Double[] b) { return ObjectArrays.concat(a, b, Double.class); } + @J2ktIncompatible @GwtIncompatible // used only from suite public abstract static class TestDoubleListGenerator implements TestListGenerator { @Override @@ -544,6 +572,7 @@ public List order(List insertionOrder) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static class SampleDoubles extends SampleElements { public SampleDoubles() { 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..a614b441d64a 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 */ +/** + * @author Kevin Bourrillion + */ @GwtCompatible(emulated = true) +@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)); } /* @@ -386,6 +403,7 @@ public void testTrimmed() { assertActuallyTrims(underSized); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { assertThat(reserialize(ImmutableIntArray.of())).isSameInstanceAs(ImmutableIntArray.of()); @@ -410,6 +428,7 @@ private static void assertDoesntActuallyTrim(ImmutableIntArray iia) { assertThat(iia.trimmed()).isSameInstanceAs(iia); } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { List> builders = @@ -441,6 +460,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 +469,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 +478,7 @@ protected List create(Integer[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static final class ImmutableIntArrayHeadSubListAsListGenerator extends TestIntegerListGenerator { @@ -468,6 +490,7 @@ protected List create(Integer[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static final class ImmutableIntArrayTailSubListAsListGenerator extends TestIntegerListGenerator { @@ -479,6 +502,7 @@ protected List create(Integer[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static final class ImmutableIntArrayMiddleSubListAsListGenerator extends TestIntegerListGenerator { @@ -491,11 +515,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 +557,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..9c279055c518 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 */ +/** + * @author Kevin Bourrillion + */ @GwtCompatible(emulated = true) +@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)); } /* @@ -388,6 +406,7 @@ public void testTrimmed() { assertActuallyTrims(underSized); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { assertThat(reserialize(ImmutableLongArray.of())).isSameInstanceAs(ImmutableLongArray.of()); @@ -412,6 +431,7 @@ private static void assertDoesntActuallyTrim(ImmutableLongArray iia) { assertThat(iia.trimmed()).isSameInstanceAs(iia); } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { List> builders = @@ -443,6 +463,7 @@ public static Test suite() { return suite; } + @J2ktIncompatible @GwtIncompatible // used only from suite private static ImmutableLongArray makeArray(Long[] values) { return ImmutableLongArray.copyOf(Arrays.asList(values)); @@ -451,6 +472,7 @@ 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 public static final class ImmutableLongArrayAsListGenerator extends TestLongListGenerator { @Override @@ -459,6 +481,7 @@ protected List create(Long[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static final class ImmutableLongArrayHeadSubListAsListGenerator extends TestLongListGenerator { @@ -470,6 +493,7 @@ protected List create(Long[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static final class ImmutableLongArrayTailSubListAsListGenerator extends TestLongListGenerator { @@ -481,6 +505,7 @@ protected List create(Long[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static final class ImmutableLongArrayMiddleSubListAsListGenerator extends TestLongListGenerator { @@ -493,11 +518,13 @@ protected List create(Long[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite private static Long[] concat(Long[] a, Long[] b) { return ObjectArrays.concat(a, b, Long.class); } + @J2ktIncompatible @GwtIncompatible // used only from suite public abstract static class TestLongListGenerator implements TestListGenerator { @Override @@ -533,6 +560,7 @@ public List order(List insertionOrder) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static class SampleLongs extends SampleElements { public SampleLongs() { 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..8f5732ff22c0 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,6 +32,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Ints#asList(int[])}. @@ -39,6 +41,7 @@ */ @GwtCompatible(emulated = true) @SuppressWarnings("cast") // redundant casts are intentional and harmless +@NullUnmarked public class IntArrayAsListTest extends TestCase { private static List asList(Integer[] values) { @@ -49,6 +52,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..f5a2b557c6d7 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,6 +36,8 @@ 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}. @@ -36,6 +45,7 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@NullMarked @SuppressWarnings("cast") // redundant casts are intentional and harmless public class IntsTest extends TestCase { private static final int[] EMPTY = {}; @@ -49,13 +59,13 @@ public class IntsTest extends TestCase { public void testHashCode() { for (int value : VALUES) { - assertEquals(((Integer) value).hashCode(), Ints.hashCode(value)); + assertThat(Ints.hashCode(value)).isEqualTo(((Integer) value).hashCode()); } } 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 +75,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 +88,190 @@ 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.valueOf(x).compareTo(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() { @@ -420,21 +539,22 @@ public void testToArray_withConversion() { List longs = Arrays.asList((long) 0, (long) 1, (long) 2); List doubles = Arrays.asList((double) 0, (double) 1, (double) 2); - 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,24 @@ 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[] {}); } 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 +589,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((Integer) 1); + assertThat(converter.convert("0")).isEqualTo((Integer) 0); + assertThat(converter.convert("-1")).isEqualTo((Integer) (-1)); + assertThat(converter.convert("0xff")).isEqualTo((Integer) 255); + assertThat(converter.convert("0xFF")).isEqualTo((Integer) 255); + assertThat(converter.convert("-0xFF")).isEqualTo((Integer) (-255)); + assertThat(converter.convert("#0000FF")).isEqualTo((Integer) 255); + assertThat(converter.convert("0666")).isEqualTo((Integer) 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 +635,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 +661,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 +671,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..76c54bdbf1ba 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,6 +32,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Longs#asList(long[])}. @@ -38,6 +40,7 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@NullUnmarked public class LongArrayAsListTest extends TestCase { private static List asList(Long[] values) { @@ -48,6 +51,7 @@ private static List asList(Long[] values) { return Longs.asList(temp); } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { List> builders = 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..d09ba263af89 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,14 +39,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 Longs}. * * @author Kevin Bourrillion */ +@NullMarked @GwtCompatible(emulated = true) -@SuppressWarnings("cast") // redundant casts are intentional and harmless public class LongsTest extends TestCase { private static final long[] EMPTY = {}; private static final long[] ARRAY1 = {(long) 1}; @@ -51,146 +59,174 @@ public class LongsTest extends TestCase { @GwtIncompatible // Long.hashCode returns different values in GWT. 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) value).hashCode()); } } + // 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.valueOf(x).compareTo(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, (long) 1)).isFalse(); + assertThat(Longs.contains(ARRAY1, (long) 2)).isFalse(); + assertThat(Longs.contains(ARRAY234, (long) 1)).isFalse(); + assertThat(Longs.contains(new long[] {(long) -1}, (long) -1)).isTrue(); + assertThat(Longs.contains(ARRAY234, (long) 2)).isTrue(); + assertThat(Longs.contains(ARRAY234, (long) 3)).isTrue(); + assertThat(Longs.contains(ARRAY234, (long) 4)).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, (long) 1)).isEqualTo(-1); + assertThat(Longs.indexOf(ARRAY1, (long) 2)).isEqualTo(-1); + assertThat(Longs.indexOf(ARRAY234, (long) 1)).isEqualTo(-1); + assertThat(Longs.indexOf(new long[] {(long) -1}, (long) -1)).isEqualTo(0); + assertThat(Longs.indexOf(ARRAY234, (long) 2)).isEqualTo(0); + assertThat(Longs.indexOf(ARRAY234, (long) 3)).isEqualTo(1); + assertThat(Longs.indexOf(ARRAY234, (long) 4)).isEqualTo(2); + assertThat(Longs.indexOf(new long[] {(long) 2, (long) 3, (long) 2, (long) 3}, (long) 3)) + .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[] {(long) 2, (long) 3})).isEqualTo(0); + assertThat(Longs.indexOf(ARRAY234, new long[] {(long) 3, (long) 4})).isEqualTo(1); + assertThat(Longs.indexOf(ARRAY234, new long[] {(long) 3})).isEqualTo(1); + assertThat(Longs.indexOf(ARRAY234, new long[] {(long) 4})).isEqualTo(2); + assertThat( + Longs.indexOf( + new long[] {(long) 2, (long) 3, (long) 3, (long) 3, (long) 3}, + new long[] {(long) 3})) + .isEqualTo(1); + assertThat( + 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})) + .isEqualTo(2); + assertThat( + 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})) + .isEqualTo(1); + assertThat( + Longs.indexOf( + new long[] {(long) 4, (long) 3, (long) 2}, + new long[] {(long) 2, (long) 3, (long) 4})) + .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, (long) 1)).isEqualTo(-1); + assertThat(Longs.lastIndexOf(ARRAY1, (long) 2)).isEqualTo(-1); + assertThat(Longs.lastIndexOf(ARRAY234, (long) 1)).isEqualTo(-1); + assertThat(Longs.lastIndexOf(new long[] {(long) -1}, (long) -1)).isEqualTo(0); + assertThat(Longs.lastIndexOf(ARRAY234, (long) 2)).isEqualTo(0); + assertThat(Longs.lastIndexOf(ARRAY234, (long) 3)).isEqualTo(1); + assertThat(Longs.lastIndexOf(ARRAY234, (long) 4)).isEqualTo(2); + assertThat(Longs.lastIndexOf(new long[] {(long) 2, (long) 3, (long) 2, (long) 3}, (long) 3)) + .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((long) 8, (long) 6, (long) 7, (long) 5, (long) 3, (long) 0, (long) 9)) + .isEqualTo((long) 9); } 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((long) 8, (long) 6, (long) 7, (long) 5, (long) 3, (long) 0, (long) 9)) + .isEqualTo((long) 0); } 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((long) 1, (long) 0, (long) 5)).isEqualTo((long) 1); + assertThat(Longs.constrainToRange((long) 1, (long) 1, (long) 5)).isEqualTo((long) 1); + assertThat(Longs.constrainToRange((long) 1, (long) 3, (long) 5)).isEqualTo((long) 3); + assertThat(Longs.constrainToRange((long) 0, (long) -5, (long) -1)).isEqualTo((long) -1); + assertThat(Longs.constrainToRange((long) 5, (long) 2, (long) 2)).isEqualTo((long) 2); + assertThrows( + IllegalArgumentException.class, () -> Longs.constrainToRange((long) 1, (long) 3, (long) 2)); + } + + 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[] {(long) 1, (long) 1, (long) 1}); + assertThat(Longs.concat(ARRAY1, ARRAY234)) + .isEqualTo(new long[] {(long) 1, (long) 2, (long) 3, (long) 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)); + + 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 +242,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 +290,32 @@ 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[] {(long) 1, (long) 0, (long) 0}); } 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(",", (long) 1, (long) 2)).isEqualTo("1,2"); + assertThat(Longs.join("", (long) 1, (long) 2, (long) 3)).isEqualTo("123"); } public void testLexicographicalComparator() { @@ -312,10 +335,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 +353,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 +371,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 +479,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 +498,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 +507,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))); + assertThat(Longs.toArray(one)).isEqualTo(ARRAY1); long[] array = {(long) 0, (long) 1, 0x0FF1C1AL}; List three = Arrays.asList((long) 0, (long) 1, 0x0FF1C1AL); - assertTrue(Arrays.equals(array, Longs.toArray(three))); + 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,21 +527,17 @@ 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((long) 0, (long) 1, null); + assertThrows(NullPointerException.class, () -> Longs.toArray(list)); } public void testToArray_withConversion() { @@ -432,21 +550,22 @@ public void testToArray_withConversion() { List longs = Arrays.asList((long) 0, (long) 1, (long) 2); List doubles = Arrays.asList((double) 0, (double) 1, (double) 2); - 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}; List list = Longs.asList(array); list.set(0, (long) 2); - assertTrue(Arrays.equals(new long[] {(long) 2, (long) 1}, array)); + assertThat(array).isEqualTo(new long[] {(long) 2, (long) 1}); array[1] = (long) 3; - assertEquals(Arrays.asList((long) 2, (long) 3), list); + assertThat(list).containsExactly((long) 2, (long) 3).inOrder(); } public void testAsList_toArray_roundTrip() { @@ -456,23 +575,24 @@ public void testAsList_toArray_roundTrip() { // Make sure it returned a copy list.set(0, (long) 4); - assertTrue(Arrays.equals(new long[] {(long) 0, (long) 1, (long) 2}, newArray)); + assertThat(newArray).isEqualTo(new long[] {(long) 0, (long) 1, (long) 2}); newArray[1] = (long) 5; - assertEquals((long) 1, (long) list.get(1)); + assertThat((long) list.get(1)).isEqualTo((long) 1); } // 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}; 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[] {(long) 1, (long) 2}); + assertThat(Longs.toArray(list.subList(2, 2))).isEqualTo(new long[] {}); } 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 +600,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((Long) 1L); + assertThat(converter.convert("0")).isEqualTo((Long) 0L); + assertThat(converter.convert("-1")).isEqualTo((Long) (-1L)); + assertThat(converter.convert("0xff")).isEqualTo((Long) 255L); + assertThat(converter.convert("0xFF")).isEqualTo((Long) 255L); + assertThat(converter.convert("-0xFF")).isEqualTo((Long) (-255L)); + assertThat(converter.convert("#0000FF")).isEqualTo((Long) 255L); + assertThat(converter.convert("0666")).isEqualTo((Long) 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 +646,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,7 +673,7 @@ 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() { @@ -563,16 +683,22 @@ public void testTryParse_radix() { radixEncodeParseAndAssertEquals((long) -8000, 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 +706,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..d3bd1cc84241 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(emulated = true) +@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..e86fd59bad3f --- /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(emulated = true) +@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..c6c4dea4d304 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,6 +32,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Shorts#asList(short[])}. @@ -38,6 +40,7 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) +@NullUnmarked public class ShortArrayAsListTest extends TestCase { private static List asList(Short[] values) { @@ -48,6 +51,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..7fdc4d646920 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 */ +@NullMarked @GwtCompatible(emulated = true) -@SuppressWarnings("cast") // redundant casts are intentional and harmless public class ShortsTest extends TestCase { private static final short[] EMPTY = {}; private static final short[] ARRAY1 = {(short) 1}; @@ -49,13 +58,13 @@ public class ShortsTest extends TestCase { public void testHashCode() { for (short value : VALUES) { - assertEquals(((Short) value).hashCode(), Shorts.hashCode(value)); + assertThat(Shorts.hashCode(value)).isEqualTo(((Short) value).hashCode()); } } 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 +74,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,12 +87,14 @@ 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) { @@ -91,179 +102,194 @@ public void testCompare() { int expected = Short.valueOf(x).compareTo(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 +300,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 +344,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 +362,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 +381,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 +489,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 +508,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 +517,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 +537,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() { @@ -449,21 +560,22 @@ public void testToArray_withConversion() { List longs = Arrays.asList((long) 0, (long) 1, (long) 2); List doubles = Arrays.asList((double) 0, (double) 1, (double) 2); - 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 +585,24 @@ 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[] {}); } 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 +610,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((Short) (short) 1); + assertThat(converter.convert("0")).isEqualTo((Short) (short) 0); + assertThat(converter.convert("-1")).isEqualTo((Short) (short) (-1)); + assertThat(converter.convert("0xff")).isEqualTo((Short) (short) 255); + assertThat(converter.convert("0xFF")).isEqualTo((Short) (short) 255); + assertThat(converter.convert("-0xFF")).isEqualTo((Short) (short) (-255)); + assertThat(converter.convert("#0000FF")).isEqualTo((Short) (short) 255); + assertThat(converter.convert("0666")).isEqualTo((Short) (short) 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..e186066e4b63 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 */ +@NullMarked @GwtCompatible(emulated = true) -@SuppressWarnings("cast") // redundant casts are intentional and harmless 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,9 +75,9 @@ 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(); } } @@ -80,56 +88,49 @@ public void testCompare() { int expected = Byte.valueOf(x).compareTo(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..944b1f3dc39c 100644 --- a/android/guava-tests/test/com/google/common/primitives/TestPlatform.java +++ b/android/guava-tests/test/com/google/common/primitives/TestPlatform.java @@ -17,8 +17,10 @@ package com.google.common.primitives; import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.NullUnmarked; @GwtCompatible(emulated = true) +@NullUnmarked class TestPlatform { static int reduceIterationsIfGwt(int iterations) { return iterations; 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..e2c5ee57a60e 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,13 @@ 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.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 +31,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 +39,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; @@ -39,17 +48,17 @@ public class UnsignedBytesTest extends TestCase { private static final byte[] VALUES = {LEAST, 127, (byte) 128, (byte) 129, GREATEST}; 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(UnsignedBytes.toInt(value))).isEqualTo(value); } assertCastFails(256L); assertCastFails(-1L); @@ -59,12 +68,12 @@ public void testCheckedCast() { public void testSaturatedCast() { for (byte value : VALUES) { - assertEquals(value, UnsignedBytes.saturatedCast(UnsignedBytes.toInt(value))); + assertThat(UnsignedBytes.saturatedCast(UnsignedBytes.toInt(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 +81,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 +95,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 +142,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 +151,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 +172,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 +195,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() { @@ -251,12 +241,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 +265,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 elements 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 +322,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 +351,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..4116438a58ca 100644 --- a/android/guava-tests/test/com/google/common/primitives/UnsignedIntegerTest.java +++ b/android/guava-tests/test/com/google/common/primitives/UnsignedIntegerTest.java @@ -14,14 +14,20 @@ 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}. @@ -29,6 +35,7 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@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,10 +270,11 @@ 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); } } + @J2ktIncompatible @GwtIncompatible // serialization public void testSerialization() { for (int a : TEST_INTS) { @@ -268,6 +282,7 @@ public void testSerialization() { } } + @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..b2bfd3bfbf81 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,6 +29,7 @@ import java.util.List; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for UnsignedInts @@ -32,6 +37,7 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@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++) { + final 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..87d6bf9e571d 100644 --- a/android/guava-tests/test/com/google/common/primitives/UnsignedLongTest.java +++ b/android/guava-tests/test/com/google/common/primitives/UnsignedLongTest.java @@ -14,14 +14,20 @@ 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}. @@ -29,6 +35,7 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@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,10 +279,11 @@ 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); } } + @J2ktIncompatible @GwtIncompatible // serialization public void testSerialization() { for (long a : TEST_LONGS) { @@ -293,6 +291,7 @@ public void testSerialization() { } } + @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..3993a416a57f 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 @@ -34,63 +40,52 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) +@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() { @@ -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++) { + final 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/ClassPathTest.java b/android/guava-tests/test/com/google/common/reflect/ClassPathTest.java index d199d1f90cb0..7f3ca3631eec 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(), ":"); @@ -399,6 +417,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 +448,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 +494,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 +519,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 +548,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..fcfcc7e92a28 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,26 @@ 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 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 +44,176 @@ * @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) + 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 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 +225,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 +248,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 +282,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 +345,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 +405,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 +442,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()); @@ -343,7 +509,7 @@ public void testInnerClassWithOneParameterConstructor() { private class InnerWithAnnotatedConstructorParameter { @SuppressWarnings("unused") // called by reflection - InnerWithAnnotatedConstructorParameter(@NullableDecl String s) {} + InnerWithAnnotatedConstructorParameter(@Nullable String s) {} } public void testInnerClassWithAnnotatedConstructorParameter() { @@ -424,7 +590,7 @@ public void run() { } public void testAnonymousClassInConstructor() { - new AnonymousClassInConstructor(); + AnonymousClassInConstructor unused = new AnonymousClassInConstructor(); } private static class AnonymousClassInConstructor { @@ -444,7 +610,7 @@ public void run() { } public void testLocalClassInInstanceInitializer() { - new LocalClassInInstanceInitializer(); + LocalClassInInstanceInitializer unused = new LocalClassInInstanceInitializer(); } private static class LocalClassInInstanceInitializer { @@ -456,7 +622,7 @@ class Local {} } public void testLocalClassInStaticInitializer() { - new LocalClassInStaticInitializer(); + LocalClassInStaticInitializer unused = new LocalClassInStaticInitializer(); } private static class LocalClassInStaticInitializer { @@ -467,8 +633,9 @@ class Local {} } } - public void testLocalClassWithSeeminglyHiddenThisInStaticInitializer_BUG() { - new LocalClassWithSeeminglyHiddenThisInStaticInitializer(); + public void testLocalClassWithSeeminglyHiddenThisInStaticInitializer_bug() { + LocalClassWithSeeminglyHiddenThisInStaticInitializer unused = + new LocalClassWithSeeminglyHiddenThisInStaticInitializer(); } /** @@ -506,7 +673,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 +697,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 +722,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 +732,7 @@ private static class Prepender { } // just for testing - private Prepender() { + private Prepender() { this(null, 0); } 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..42dc61a9f2db 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))); + assertNull(map.putInstance(Integer.class, Integer.valueOf(5))); - 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,11 +135,9 @@ 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))); @@ -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..eda28b5ec790 100644 --- a/android/guava-tests/test/com/google/common/reflect/SubtypeTester.java +++ b/android/guava-tests/test/com/google/common/reflect/SubtypeTester.java @@ -30,6 +30,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. @@ -64,6 +66,7 @@ *

    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. */ @@ -78,7 +81,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 +108,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..72e452beee43 100644 --- a/android/guava-tests/test/com/google/common/reflect/TypeTokenResolutionTest.java +++ b/android/guava-tests/test/com/google/common/reflect/TypeTokenResolutionTest.java @@ -17,6 +17,7 @@ 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; @@ -29,6 +30,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 +38,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 +181,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()); } @@ -246,14 +251,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() { 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..cfaf9f6ffdd1 100644 --- a/android/guava-tests/test/com/google/common/reflect/TypeTokenTest.java +++ b/android/guava-tests/test/com/google/common/reflect/TypeTokenTest.java @@ -17,6 +17,7 @@ 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; @@ -44,6 +45,7 @@ import java.util.List; import java.util.Map; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Test cases for {@link TypeToken}. @@ -52,6 +54,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 +67,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. } } @@ -402,7 +412,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 +420,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 +428,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()); 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,7 +442,7 @@ 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()); assertEquals(TypeToken.of(Object.class), new TypeToken() {}.getGenericSuperclass()); @@ -574,9 +584,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 +759,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 +775,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 +798,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() { @@ -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(); @@ -1450,17 +1459,19 @@ 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()); } @@ -1587,11 +1598,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 +1661,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 { @@ -1747,6 +1748,7 @@ Type type() { } } + @SuppressWarnings("RestrictedApiChecker") // crashes under JDK8, which EP no longer supports public void testRejectTypeVariable_withOwnerType() { // Neither has subclass assertHasTypeVariable(new From().new To().type()); @@ -1793,7 +1795,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 +1809,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 +1841,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 +1885,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..785b3a65872a 100644 --- a/android/guava-tests/test/com/google/common/reflect/TypesTest.java +++ b/android/guava-tests/test/com/google/common/reflect/TypesTest.java @@ -18,6 +18,7 @@ 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; @@ -36,6 +37,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 +45,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 +84,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 +120,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 +132,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 +232,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 +259,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 +304,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( @@ -372,11 +371,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..3688400f8ca5 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) +@NullUnmarked abstract class AbstractAbstractFutureTest extends TestCase { private TestedFuture future; private AbstractFuture delegate; @@ -329,28 +334,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 +351,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 +367,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 +382,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 +430,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 +449,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 90% 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..10e6942ab7de 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,6 +20,7 @@ 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; @@ -30,6 +31,7 @@ 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 +75,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 +84,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 = @@ -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 { @@ -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 { @@ -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,53 +1728,65 @@ 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); @@ -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..82126ef425b0 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,8 @@ 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.testing.TearDown; import com.google.common.testing.TearDownStack; @@ -27,15 +29,16 @@ 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); @@ -68,7 +71,6 @@ protected final void tearDown() { thrownByExecutionThread); } - public void testServiceStartStop() throws Exception { WaitOnRunService service = new WaitOnRunService(); assertFalse(service.startUpCalled); @@ -85,7 +87,6 @@ public void testServiceStartStop() throws Exception { executionThread.join(); } - public void testServiceStopIdempotence() throws Exception { WaitOnRunService service = new WaitOnRunService(); @@ -102,7 +103,6 @@ public void testServiceStopIdempotence() throws Exception { executionThread.join(); } - public void testServiceExitingOnItsOwn() throws Exception { WaitOnRunService service = new WaitOnRunService(); service.expectedShutdownState = Service.State.RUNNING; @@ -173,18 +173,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 +208,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 +259,6 @@ protected Executor executor() { } } - public void testServiceThrowOnShutDown() throws Exception { ThrowOnShutDown service = new ThrowOnShutDown(); @@ -310,12 +297,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,7 +316,6 @@ public void execute(Runnable command) {} protected void run() throws Exception {} } - public void testStopWhileStarting_runNotCalled() throws Exception { final CountDownLatch started = new CountDownLatch(1); FakeService service = @@ -361,7 +345,6 @@ public void testStop_noStart() { assertEquals(0, service.shutdownCalled); } - public void testDefaultService() throws InterruptedException { WaitOnRunService service = new WaitOnRunService(); service.startAsync().awaitRunning(); @@ -386,14 +369,12 @@ 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 { 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..0b5774b9ac09 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 @@ -406,7 +409,7 @@ private boolean complete(@NullableDecl V v, @NullableDecl Throwable t, int final } static final CancellationException cancellationExceptionWithCause( - @NullableDecl String message, @NullableDecl Throwable cause) { + @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..48c4d41ac0de 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; @@ -88,12 +92,8 @@ public void testCancel_notDoneNoInterrupt() throws Exception { assertTrue(future.isCancelled()); assertTrue(future.isDone()); assertNull(tryInternalFastPathGetFailure(future)); - try { - future.get(); - fail("Expected CancellationException"); - } catch (CancellationException e) { - assertNotNull(e.getCause()); - } + CancellationException e = assertThrows(CancellationException.class, () -> future.get()); + assertNotNull(e.getCause()); } public void testCancel_notDoneInterrupt() throws Exception { @@ -102,12 +102,8 @@ public void testCancel_notDoneInterrupt() throws Exception { assertTrue(future.isCancelled()); assertTrue(future.isDone()); assertNull(tryInternalFastPathGetFailure(future)); - try { - future.get(); - fail("Expected CancellationException"); - } catch (CancellationException e) { - assertNotNull(e.getCause()); - } + CancellationException e = assertThrows(CancellationException.class, () -> future.get()); + assertNotNull(e.getCause()); } 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/AbstractFutureFallbackAtomicHelperTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureFallbackAtomicHelperTest.java index 208cf24831e1..b62fe241f657 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,6 +14,8 @@ package com.google.common.util.concurrent; +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; + import com.google.common.collect.ImmutableSet; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -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,20 +33,15 @@ *

    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 @@ -53,17 +51,16 @@ public class AbstractFutureFallbackAtomicHelperTest extends TestCase { * This classloader disallows {@link sun.misc.Unsafe}, which will prevent us from selecting our * preferred strategy {@code UnsafeAtomicHelper}. */ - private static final ClassLoader NO_UNSAFE = - getClassLoader(ImmutableSet.of(sun.misc.Unsafe.class.getName())); + private static final ClassLoader NO_UNSAFE = getClassLoader(ImmutableSet.of("sun.misc.Unsafe")); /** * This classloader disallows {@link sun.misc.Unsafe} and {@link AtomicReferenceFieldUpdater}, - * which will prevent us from selecting our {@code SafeAtomicHelper} strategy. + * which will prevent us from selecting our {@code AtomicReferenceFieldUpdaterAtomicHelper} + * strategy. */ private static final ClassLoader NO_ATOMIC_REFERENCE_FIELD_UPDATER = getClassLoader( - ImmutableSet.of( - sun.misc.Unsafe.class.getName(), AtomicReferenceFieldUpdater.class.getName())); + ImmutableSet.of("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 @@ -83,7 +80,7 @@ public static TestSuite suite() { public void runTest() throws Exception { // First ensure that our classloaders are initializing the correct helper versions checkHelperVersion(getClass().getClassLoader(), "UnsafeAtomicHelper"); - checkHelperVersion(NO_UNSAFE, "SafeAtomicHelper"); + checkHelperVersion(NO_UNSAFE, "AtomicReferenceFieldUpdaterAtomicHelper"); checkHelperVersion(NO_ATOMIC_REFERENCE_FIELD_UPDATER, "SynchronizedHelper"); // Run the corresponding AbstractFutureTest test method in a new classloader that disallows @@ -140,4 +137,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..218025105088 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,26 @@ 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.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; @@ -46,13 +59,15 @@ 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(); @@ -94,13 +109,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 +120,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,7 +143,7 @@ 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 { @@ -153,12 +158,8 @@ 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 { @@ -263,13 +264,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 +288,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 +355,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\\]\\]"); } @@ -426,6 +428,9 @@ public void run() { */ public void testFutureBash() { + if (isWindows()) { + return; // TODO: b/136041958 - Running very slowly on Windows CI. + } final CyclicBarrier barrier = new CyclicBarrier( 6 // for the setter threads @@ -435,10 +440,10 @@ public void testFutureBash() { final ExecutorService executor = Executors.newFixedThreadPool(barrier.getParties()); final AtomicReference> currentFuture = Atomics.newReference(); final AtomicInteger numSuccessfulSetCalls = new AtomicInteger(); - Callable completeSuccessfullyRunnable = - new Callable() { + 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 +451,12 @@ public Void call() { return null; } }; - Callable completeExceptionallyRunnable = - new Callable() { + Callable<@Nullable Void> completeExceptionallyRunnable = + new Callable<@Nullable Void>() { Exception failureCause = new Exception("setException"); @Override - public Void call() { + public @Nullable Void call() { if (currentFuture.get().setException(failureCause)) { numSuccessfulSetCalls.incrementAndGet(); } @@ -459,10 +464,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 +475,12 @@ public Void call() { return null; } }; - Callable setFutureCompleteSuccessfullyRunnable = - new Callable() { - ListenableFuture future = Futures.immediateFuture("setFuture"); + Callable<@Nullable Void> setFutureCompleteSuccessfullyRunnable = + new Callable<@Nullable Void>() { + ListenableFuture future = immediateFuture("setFuture"); @Override - public Void call() { + public @Nullable Void call() { if (currentFuture.get().setFuture(future)) { numSuccessfulSetCalls.incrementAndGet(); } @@ -483,13 +488,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>() { + ListenableFuture future = immediateFailedFuture(new Exception("setFuture")); @Override - public Void call() { + public @Nullable Void call() { if (currentFuture.get().setFuture(future)) { numSuccessfulSetCalls.incrementAndGet(); } @@ -497,12 +501,12 @@ public Void call() { return null; } }; - Callable setFutureCancelRunnable = - new Callable() { - ListenableFuture future = Futures.immediateCancelledFuture(); + Callable<@Nullable Void> setFutureCancelRunnable = + new Callable<@Nullable Void>() { + ListenableFuture future = immediateCancelledFuture(); @Override - public Void call() { + public @Nullable Void call() { if (currentFuture.get().setFuture(future)) { numSuccessfulSetCalls.incrementAndGet(); } @@ -534,7 +538,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 +563,15 @@ 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)); 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; } @@ -607,6 +611,9 @@ public Void call() throws Exception { // setFuture and cancel() interact in more complicated ways than the other setters. public void testSetFutureCancelBash() { + if (isWindows()) { + return; // TODO: b/136041958 - Running very slowly on Windows CI. + } final int size = 50; final CyclicBarrier barrier = new CyclicBarrier( @@ -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,7 +690,7 @@ 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; @@ -751,21 +758,21 @@ public void testSetFutureCancelBash_withDoneFuture() { final AtomicReference> currentFuture = Atomics.newReference(); final AtomicBoolean setFutureSuccess = new AtomicBoolean(); final AtomicBoolean cancellationSuccess = new AtomicBoolean(); - Callable cancelRunnable = - new Callable() { + 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; @@ -839,6 +846,7 @@ 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 public void testSetFutureToString_stackOverflow() { @@ -1010,7 +1018,7 @@ public void run() { ranImmediately.set(true); } }, - MoreExecutors.directExecutor()); + directExecutor()); assertThat(ranImmediately.get()).isTrue(); } }; @@ -1026,7 +1034,7 @@ public void testListenersExecuteImmediately_afterWaiterWakesUp() throws Exceptio 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 } @@ -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(); 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(); @@ -1154,12 +1170,8 @@ public void addListener(Runnable listener, Executor executor) { 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) { @@ -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..f46960f55a2a 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.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); @@ -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); @@ -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,14 +137,12 @@ 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 { @@ -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() { + final 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() { + final 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..24f7c9b2e21c 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,12 @@ 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.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,6 +32,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Delayed; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; @@ -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,12 +110,9 @@ 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()); } @@ -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()); } @@ -206,7 +203,7 @@ protected ScheduledExecutorService executor() { @Override protected Scheduler scheduler() { - return newFixedDelaySchedule(0, 1, TimeUnit.MILLISECONDS); + return newFixedDelaySchedule(0, 1, MILLISECONDS); } }; @@ -215,7 +212,7 @@ 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 { @@ -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,14 +284,12 @@ 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 { @@ -310,9 +301,9 @@ private class TestService extends AbstractScheduledService { 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; + 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 { + public 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 { + 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(); } - }; - service.startAsync().awaitRunning(); - inGetNextSchedule.await(); - service.stopAsync(); - } - } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + TestCustomScheduler scheduler = new TestCustomScheduler(); + Cancellable 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); + } + + 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 { + 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() {} - 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 Executors.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 Executors.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..f3828d7dacaa 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,7 +19,9 @@ 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; @@ -30,16 +32,17 @@ import java.lang.Thread.UncaughtExceptionHandler; 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; @@ -330,7 +333,6 @@ protected void doStop() { } } - public void testAwaitTerminated() throws Exception { final NoOpService service = new NoOpService(); Thread waiter = @@ -348,8 +350,7 @@ public void run() { assertFalse(waiter.isAlive()); } - - public void testAwaitTerminated_FailedService() throws Exception { + public void testAwaitTerminated_failedService() throws Exception { final ManualSwitchedService service = new ManualSwitchedService(); final AtomicReference exception = Atomics.newReference(); Thread waiter = @@ -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,40 +603,25 @@ 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(); service.startAsync(); @@ -681,7 +641,6 @@ public void run() { assertFalse(thread + " is deadlocked", thread.isAlive()); } - public void testListenerDoesntDeadlockOnStartAndWaitFromRunning() throws Exception { final NoOpThreadedService service = new NoOpThreadedService(); service.addListener( @@ -692,11 +651,10 @@ 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(); service.addListener( @@ -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/AggregateFutureStateFallbackAtomicHelperTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AggregateFutureStateFallbackAtomicHelperTest.java index fec93946f794..9823f5fcc8d7 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,11 +41,12 @@ * * * 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 { /** @@ -66,7 +68,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())); 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..7e3d63320af5 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); 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,7 @@ class Counter extends CheckedRunnable { aa = a; } + @Override public void realRun() { for (; ; ) { boolean done = true; @@ -294,7 +272,6 @@ 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); for (int i = 0; i < SIZE; i++) { 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..73d6025a26e6 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); 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..994ddaca9906 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.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"; - final AtomicLong sum = new AtomicLong(); final AtomicLongMap map = AtomicLongMap.create(); ExecutorService threadPool = Executors.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..0eccd0c43be0 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,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.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.common.testing.NullPointerTester; @@ -26,6 +27,7 @@ import java.util.Random; import java.util.Set; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link AtomicLongMap}. @@ -33,12 +35,14 @@ * @author mike nonemacher */ @GwtCompatible(emulated = true) +@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")); 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..75de1d6df65f 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,15 +17,20 @@ 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}. @@ -33,8 +38,10 @@ * @author Isaac Shum */ @GwtCompatible(emulated = true) +@NullUnmarked public class CallablesTest extends TestCase { + @J2ktIncompatible // TODO(b/324550390): Enable public void testReturning() throws Exception { assertNull(Callables.returning(null).call()); @@ -45,6 +52,7 @@ public void testReturning() throws Exception { assertSame(value, callable.call()); } + @J2ktIncompatible @GwtIncompatible public void testAsAsyncCallable() throws Exception { final String expected = "MyCallableString"; @@ -57,12 +65,13 @@ 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(); @@ -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() { + 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() { + 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..c03230315189 --- /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) { + final 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..da262806a3ec 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.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,11 +33,11 @@ * @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(); CountDownLatch countDownLatch = new CountDownLatch(3); @@ -48,7 +49,7 @@ 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() { @@ -67,7 +68,6 @@ public void run() { assertEquals(1, runCalled.get()); } - public void testExecute_idempotentConcurrently() throws InterruptedException { final CountDownLatch okayToRun = new CountDownLatch(1); final AtomicInteger runCalled = new AtomicInteger(); @@ -103,7 +103,6 @@ public void run() { assertEquals(1, runCalled.get()); } - public void testAddAfterRun() throws Exception { // Run the previous test testRunOnPopulatedList(); @@ -111,7 +110,7 @@ public void testAddAfterRun() throws Exception { // 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)); + assertTrue(countDownLatch.await(1L, SECONDS)); } public void testOrdering() throws Exception { @@ -125,7 +124,7 @@ public void run() { integer.compareAndSet(expectedCount, expectedCount + 1); } }, - MoreExecutors.directExecutor()); + directExecutor()); } list.execute(); assertEquals(10, integer.get()); 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..ee51de22fded 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 @@ -21,6 +21,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.testing.GcFinalization; import com.google.common.testing.TestLogHandler; @@ -34,17 +35,19 @@ 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 @@ -76,7 +79,8 @@ 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()); + ListenableFuture<@Nullable Void> secondFuture = + serializer.submitAsync(secondCallable, directExecutor()); TestCallable thirdCallable = new TestCallable(Futures.immediateFuture(null)); @SuppressWarnings({"unused", "nullness"}) Future possiblyIgnoredError1 = serializer.submitAsync(thirdCallable, directExecutor()); @@ -88,10 +92,9 @@ public void testCancellationDoesNotViolateSerialization() { assertThat(thirdCallable.called).isTrue(); } - public void testCancellationMultipleThreads() throws Exception { final BlockingCallable blockingCallable = new BlockingCallable(); - ListenableFuture unused = serializer.submit(blockingCallable, executor); + ListenableFuture<@Nullable Void> unused = serializer.submit(blockingCallable, executor); ListenableFuture future2 = serializer.submit( new Callable() { @@ -112,14 +115,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); + ListenableFuture<@Nullable Void> future1 = serializer.submit(blockingCallable, executor); ListenableFuture future2 = serializer.submit( new Callable() { @@ -145,22 +147,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(); + final 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; } }, @@ -197,9 +200,9 @@ public void execute(Runnable task) { final 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,13 +228,13 @@ public Void call() { } public void testAvoidsStackOverflow_manySubmitted() throws Exception { - final SettableFuture settableFuture = SettableFuture.create(); - ArrayList> results = new ArrayList<>(50_001); + final 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; } }, @@ -244,12 +247,12 @@ public ListenableFuture call() { } public void testAvoidsStackOverflow_manyCancelled() throws Exception { - final SettableFuture settableFuture = SettableFuture.create(); - ListenableFuture unused = + final 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,12 +275,12 @@ public Integer call() { } public void testAvoidsStackOverflow_alternatingCancelledAndSubmitted() throws Exception { - final SettableFuture settableFuture = SettableFuture.create(); - ListenableFuture unused = + final 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; } }, @@ -326,8 +329,8 @@ 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<>(); @@ -347,12 +350,12 @@ public Integer call() { }, service) .get(); - final SettableFuture settableFuture = SettableFuture.create(); + final 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 +365,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 +389,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; } @@ -419,14 +422,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(); @@ -447,17 +450,17 @@ public boolean isRunning() { } } - 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..922e02747e4f 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,63 +46,59 @@ 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) { 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..3339f08877d5 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,20 +23,24 @@ 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}. */ +@NullMarked @GwtCompatible(emulated = true) public class FluentFutureTest extends TestCase { public void testFromFluentFuture() { @@ -74,9 +78,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,12 +93,12 @@ 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>() { @@ -101,7 +108,7 @@ public ListenableFuture> apply(Throwable input) { } }, directExecutor()); - assertThat(f.get()).isEqualTo(RuntimeException.class); + assertThat(f.get()).isEqualTo(CustomRuntimeException.class); } public void testTransform() throws Exception { @@ -132,19 +139,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..f2474e7f6ff3 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 @@ -17,8 +17,10 @@ package com.google.common.util.concurrent; 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); 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..24eff3ef47b3 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 @@ -26,12 +26,14 @@ import com.google.common.testing.ForwardingWrapperTester; import java.lang.reflect.Method; import java.util.Arrays; +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; @@ -61,7 +63,7 @@ static void testForwardingObject(final Class for 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); 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..8b4b63614bda 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,9 +179,10 @@ 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; @@ -170,17 +193,21 @@ public MockCallback(Throwable 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..7c8cf27d1914 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.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; +import org.jspecify.annotations.Nullable; /** 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(Futures.<@Nullable String>immediateFuture(null))).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..b2cb616bd710 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) +@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..38f508e1f8ac 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,6 +69,8 @@ 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; @@ -89,13 +94,15 @@ 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 */ +@NullMarked @GwtCompatible(emulated = true) public class FuturesTest extends TestCase { private static final Logger aggregateFutureLogger = @@ -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,27 +184,23 @@ 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) { return new Predicate() { @@ -248,12 +235,14 @@ 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 transformedFuture = transform(nullFuture, constant(null), directExecutor()); assertNull(getDone(transformedFuture)); } + @J2ktIncompatible // TODO(b/324550390): Enable public void testTransform_genericsHierarchy() throws Exception { ListenableFuture future = immediateFuture(null); final BarChild barChild = new BarChild(); @@ -273,8 +262,9 @@ public BarChild apply(Foo unused) { * stack-overflow tests work. It must depend on the exact place the error occurs. */ @AndroidIncompatible + @J2ktIncompatible @GwtIncompatible // StackOverflowError - public void testTransform_StackOverflow() throws Exception { + public void testTransform_stackOverflow() throws Exception { { /* * Initialize all relevant classes before running the test, which may otherwise poison any @@ -290,21 +280,18 @@ public void testTransform_StackOverflow() throws Exception { for (int i = 0; i < 10000; i++) { output = transform(output, identity(), directExecutor()); } - try { - root.set("foo"); - fail(); - } catch (StackOverflowError expected) { - } + assertThrows(StackOverflowError.class, () -> root.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 +304,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 +327,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 +350,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 +363,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 +377,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,8 +385,8 @@ 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); @@ -431,22 +407,20 @@ 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 { @@ -504,8 +478,9 @@ public ListenableFuture apply(Foo unused) { * stack-overflow tests work. It must depend on the exact place the error occurs. */ @AndroidIncompatible + @J2ktIncompatible @GwtIncompatible // StackOverflowError - public void testTransformAsync_StackOverflow() throws Exception { + public void testTransformAsync_stackOverflow() throws Exception { { /* * Initialize all relevant classes before running the test, which may otherwise poison any @@ -521,21 +496,18 @@ public void testTransformAsync_StackOverflow() throws Exception { for (int i = 0; i < 10000; i++) { output = transformAsync(output, asyncIdentity(), directExecutor()); } - try { - root.set("foo"); - fail(); - } catch (StackOverflowError expected) { - } + assertThrows(StackOverflowError.class, () -> root.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 +520,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 +543,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 +566,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 +578,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 +588,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. */ @@ -687,14 +642,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 +657,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 +672,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 +696,7 @@ private static void runGetIdempotencyTest( } } + @J2ktIncompatible @GwtIncompatible // used only in GwtIncompatible tests private static Function newOneTimeExceptionThrower() { return new Function() { @@ -753,11 +707,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 +723,7 @@ public Integer apply(Integer from) { if (++calls > 1) { fail(); } - throw new MyError(); + throw new SomeError(); } }; } @@ -790,7 +745,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,8 +757,8 @@ 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); @@ -831,6 +786,7 @@ public ListenableFuture apply(Object input) throws Exception { } } + @J2ktIncompatible @GwtIncompatible // lazyTransform public void testLazyTransform() throws Exception { FunctionSpy spy = new FunctionSpy<>(constant("bar")); @@ -843,6 +799,7 @@ public void testLazyTransform() throws Exception { spy.verifyCallCount(2); } + @J2ktIncompatible @GwtIncompatible // lazyTransform public void testLazyTransform_exception() throws Exception { final RuntimeException exception = new RuntimeException("deliberate"); @@ -854,18 +811,12 @@ 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 { @@ -891,7 +842,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); } }; } @@ -927,18 +878,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 { @@ -967,6 +911,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(); @@ -996,12 +941,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 { @@ -1078,9 +1023,9 @@ public void testCatchingAsync_resultCancelledBeforeFallback() throws Exception { assertFalse(primary.wasInterrupted()); } + @J2ktIncompatible @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(); @@ -1104,6 +1049,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,21 +1064,18 @@ 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); @@ -1153,26 +1096,25 @@ 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); @@ -1250,6 +1192,7 @@ public Integer apply(Throwable t) { fallback.verifyCallCount(1); } + @J2ktIncompatible @GwtIncompatible // non-Throwable exceptionType public void testCatching_inputCancelledWithoutFallback() throws Exception { Function fallback = unexpectedFunction(); @@ -1279,12 +1222,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()); } /* @@ -1344,7 +1286,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 +1294,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,22 +1304,21 @@ 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 { + public void testCatching_stackOverflow() throws Exception { { /* * Initialize all relevant classes before running the test, which may otherwise poison any @@ -1393,21 +1335,18 @@ public void testCatching_StackOverflow() throws Exception { for (int i = 0; i < 10000; i++) { output = catching(output, MyException.class, identity(), directExecutor()); } - try { - root.setException(new MyException()); - fail(); - } catch (StackOverflowError expected) { - } + assertThrows(StackOverflowError.class, () -> root.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 +1359,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 +1382,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 +1408,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 +1423,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,22 +1433,21 @@ 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 { + public void testCatchingAsync_stackOverflow() throws Exception { { /* * Initialize all relevant classes before running the test, which may otherwise poison any @@ -1528,21 +1464,18 @@ public void testCatchingAsync_StackOverflow() throws Exception { for (int i = 0; i < 10000; i++) { output = catchingAsync(output, MyException.class, asyncIdentity(), directExecutor()); } - try { - root.setException(new MyException()); - fail(); - } catch (StackOverflowError expected) { - } + assertThrows(StackOverflowError.class, () -> root.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 +1489,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 +1513,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 +1539,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 +1551,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 +1565,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 +1589,7 @@ public ListenableFuture apply(X t) { } private static AsyncFunction constantAsyncFunction( - final ListenableFuture output) { + final @Nullable ListenableFuture output) { return new AsyncFunction() { @Override public ListenableFuture apply(I input) { @@ -1674,14 +1598,16 @@ public ListenableFuture apply(I input) { }; } - public void testTransformAsync_genericsWildcard_AsyncFunction() throws Exception { + @J2ktIncompatible // Wildcard generics + public void testTransformAsync_genericsWildcard_asyncFunction() throws Exception { ListenableFuture nullFuture = immediateFuture(null); ListenableFuture chainedFuture = transformAsync(nullFuture, constantAsyncFunction(nullFuture), directExecutor()); assertNull(getDone(chainedFuture)); } - 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(); AsyncFunction function = @@ -1697,17 +1623,14 @@ 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) { - } + assertThrows(TimeoutException.class, () -> future.get(1, MILLISECONDS)); } public void testTransformAsync_asyncFunction_error() throws InterruptedException { @@ -1723,33 +1646,28 @@ 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); @@ -1765,26 +1683,20 @@ 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(); @@ -1834,31 +1746,26 @@ 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); @@ -1874,25 +1781,20 @@ 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(); @@ -1925,8 +1827,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(); @@ -1961,12 +1863,8 @@ 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 { @@ -1986,7 +1884,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); @@ -2005,17 +1903,13 @@ 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"); AsyncCallable callable = @@ -2028,38 +1922,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 @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); @@ -2074,25 +1961,19 @@ 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(); @@ -2114,7 +1995,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 +2006,8 @@ public void run() { assertFalse(callableCalled.get()); } - private static AsyncCallable constantAsyncCallable(final ListenableFuture returnValue) { + private static AsyncCallable constantAsyncCallable( + final @Nullable ListenableFuture returnValue) { return new AsyncCallable() { @Override public ListenableFuture call() { @@ -2163,7 +2045,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 +2078,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 +2088,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 +2098,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 +2107,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 +2117,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 +2128,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 +2145,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 +2194,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 +2221,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 +2235,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,29 +2284,27 @@ 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 { @@ -2480,13 +2328,9 @@ 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 { @@ -2510,44 +2354,41 @@ 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(); AsyncCallable combiner = @@ -2565,6 +2406,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,8 +2432,8 @@ 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(); @@ -2674,16 +2516,13 @@ 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(); @@ -2700,29 +2539,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(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(); @@ -2743,19 +2577,18 @@ 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 { @@ -2806,16 +2639,13 @@ 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(); @@ -2839,25 +2669,24 @@ 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(); @@ -2878,19 +2707,18 @@ 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 { @@ -2912,15 +2740,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 +2782,7 @@ public Long call() { } @AndroidIncompatible + @J2ktIncompatible @GwtIncompatible public void testWhenAllComplete_releasesInputFuturesUponCancellation() throws Exception { SettableFuture future = SettableFuture.create(); @@ -2979,6 +2806,7 @@ public Long call() { } @AndroidIncompatible + @J2ktIncompatible @GwtIncompatible public void testWhenAllSucceed_releasesCallable() throws Exception { AsyncCallable combiner = @@ -3008,6 +2836,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 +2859,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 +2993,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 +3045,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,6 +3077,7 @@ public ListenableFuture> merged( * forever in the case of failure. */ @CanIgnoreReturnValue + @J2ktIncompatible @GwtIncompatible // threads static V pseudoTimedGetUninterruptibly(final Future input, long timeout, TimeUnit unit) throws ExecutionException, TimeoutException { @@ -3264,7 +3096,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 +3109,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 +3189,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, @@ -3375,13 +3209,13 @@ private static List conditionalPseudoTimedGetUninterruptibly( : 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 +3226,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 +3259,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 +3269,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 +3287,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 +3305,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 +3322,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 +3337,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 +3347,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); @@ -3530,7 +3358,6 @@ private static void doTestSuccessfulAsList_resultCancelledRacingInputDone() thro */ final SettableFuture future1 = SettableFuture.create(); final SettableFuture future2 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = successfulAsList(future1, future2); future1.addListener( @@ -3565,7 +3392,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 +3407,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 +3425,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 +3448,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 +3494,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 +3518,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 +3575,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 +3603,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++; } @@ -3854,6 +3669,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 +3694,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 +3713,7 @@ public void testCompletionOrderMixedBagOTypes() throws Exception { } } + @J2ktIncompatible @GwtIncompatible // ClassSanityTester public void testFutures_nullChecks() throws Exception { new ClassSanityTester() @@ -3904,12 +3722,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. 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..aee24a5f60bf 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 @@ -20,18 +20,22 @@ 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 +47,7 @@ * * @author Justin T. Sampson */ - +@NullUnmarked public class GeneratedMonitorTest extends TestCase { public static TestSuite suite() { @@ -58,7 +62,7 @@ public static TestSuite suite() { } } - assertEquals(548, suite.testCount()); + assertEquals(980, suite.testCount()); return suite; } @@ -186,14 +190,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 +231,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 +249,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: @@ -436,7 +462,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; @@ -470,7 +500,7 @@ public void run() { runChosenTest(); } }; - final FutureTask task = new FutureTask<>(runChosenTest, null); + final FutureTask<@Nullable Void> task = new FutureTask<>(runChosenTest, null); startThread( new Runnable() { @Override @@ -619,21 +649,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 +680,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); } } @@ -718,8 +749,15 @@ 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 +765,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()); @@ -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..92a7b5accccd 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,26 +16,28 @@ 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.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() { + InterruptibleTask<@Nullable Void> task = + new InterruptibleTask<@Nullable Void>() { @Override - Void runInterruptibly() throws Exception { + @Nullable Void runInterruptibly() throws Exception { BrokenChannel bc = new BrokenChannel(); bc.doBegin(); isInterruptibleRegistered.countDown(); @@ -54,22 +56,21 @@ 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.start(); isInterruptibleRegistered.await(); - try { - task.interruptTask(); - fail(); - } catch (RuntimeException expected) { - assertThat(expected) - .hasMessageThat() - .isEqualTo("I bet you didn't think Thread.interrupt could throw"); - } + 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. - runner.join(TimeUnit.SECONDS.toMillis(10)); + runner.join(SECONDS.toMillis(10)); } static final class BrokenChannel extends AbstractInterruptibleChannel { @@ -88,13 +89,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() { + final 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 +126,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 +151,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..7460daad43cf 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()); 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..341cfa83b099 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 @@ -20,6 +20,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.FilePermission; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.security.CodeSource; @@ -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) { @@ -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,47 +472,41 @@ 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); @@ -531,23 +535,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 +591,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 +606,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 +640,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 +658,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 +684,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 +730,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 +749,7 @@ RunnableShouldThrow(Class exceptionClass) { this.exceptionClass = exceptionClass; } + @Override public final void run() { try { realRun(); @@ -758,6 +769,7 @@ ThreadShouldThrow(Class exceptionClass) { this.exceptionClass = exceptionClass; } + @Override public final void run() { try { realRun(); @@ -771,6 +783,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 +799,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 +813,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 +829,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,6 +843,7 @@ 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; } @@ -833,6 +851,7 @@ public String call() { public Callable latchAwaitingStringTask(final CountDownLatch latch) { return new CheckedCallable() { + @Override protected String realCall() { try { latch.await(); @@ -845,6 +864,7 @@ protected String realCall() { public Runnable awaiter(final CountDownLatch latch) { return new CheckedRunnable() { + @Override public void realRun() throws InterruptedException { await(latch); } @@ -887,36 +907,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 +951,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,12 +960,14 @@ 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); } @@ -946,6 +975,7 @@ protected void realRun() throws InterruptedException { public Runnable possiblyInterruptedRunnable(final long timeoutMillis) { return new CheckedRunnable() { + @Override protected void realRun() { try { delay(timeoutMillis); @@ -956,6 +986,7 @@ protected void realRun() { } public class MediumPossiblyInterruptedRunnable extends CheckedRunnable { + @Override protected void realRun() { try { delay(MEDIUM_DELAY_MS); @@ -965,6 +996,7 @@ protected void realRun() { } public class LongPossiblyInterruptedRunnable extends CheckedRunnable { + @Override protected void realRun() { try { delay(LONG_DELAY_MS); @@ -975,6 +1007,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); } @@ -988,10 +1021,12 @@ public static TrackedRunnable trackedRunnable(final 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 +1040,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 +1053,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 +1066,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 +1079,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 +1092,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 +1145,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 +1158,7 @@ public CheckedBarrier(int parties) { super(parties); } + @Override public int await() { try { return super.await(2 * LONG_DELAY_MS, MILLISECONDS); @@ -1129,7 +1172,7 @@ public int await() { } } - void checkEmpty(BlockingQueue q) { + void checkEmpty(BlockingQueue q) { try { assertTrue(q.isEmpty()); assertEquals(0, q.size()); 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..d762292408f9 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 @@ -33,6 +33,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 +41,7 @@ * @author Sven Mawson * @author Kurt Alfred Kluever */ +@NullUnmarked public class JdkFutureAdaptersTest extends TestCase { private static final String DATA1 = "data"; @@ -100,7 +102,6 @@ public void testListenInPoolThreadIgnoresExecutorWhenDelegateIsDone() throws Exc assertTrue(listenableFuture.isDone()); } - public void testListenInPoolThreadUsesGivenExecutor() throws Exception { ExecutorService executorService = newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true).build()); @@ -125,7 +126,6 @@ public void testListenInPoolThreadUsesGivenExecutor() throws Exception { assertTrue(listenableFuture.isDone()); } - public void testListenInPoolThreadCustomExecutorInterrupted() throws Exception { final CountDownLatch submitSuccessful = new CountDownLatch(1); ExecutorService executorService = @@ -133,7 +133,7 @@ public void testListenInPoolThreadCustomExecutorInterrupted() throws Exception { 0, Integer.MAX_VALUE, 60L, - TimeUnit.SECONDS, + SECONDS, new SynchronousQueue(), new ThreadFactoryBuilder().setDaemon(true).build()) { @Override @@ -235,7 +235,6 @@ public synchronized void run() { } } - @SuppressWarnings("IsInstanceIncompatibleType") // intentional. public void testListenInPoolThreadRunsListenerAfterRuntimeException() throws Exception { RuntimeExceptionThrowingFuture input = new RuntimeExceptionThrowingFuture<>(); 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..4661e1df9a3f 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.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; @@ -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..8cef591033a2 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.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; @@ -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..7cf21dd72298 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 @@ -31,8 +31,10 @@ 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,7 +131,6 @@ public void testEnqueueAndDispatch_withLabeledExceptions() { logHandler.getStoredLogRecords().get(0).getMessage()); } - public void testEnqueueAndDispatch_multithreaded() throws InterruptedException { Object listener = new Object(); ExecutorService service = Executors.newFixedThreadPool(4); @@ -153,7 +154,6 @@ public void testEnqueueAndDispatch_multithreaded() throws InterruptedException { } } - public void testEnqueueAndDispatch_multithreaded_withThrowingRunnable() throws InterruptedException { Object listener = new Object(); 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..88299c24c217 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 { 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..1d00fb2ce6a9 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 @@ -36,8 +36,12 @@ 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; @@ -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,7 +94,6 @@ public class MoreExecutorsTest extends JSR166TestCase { public void run() {} }; - public void testDirectExecutorServiceServiceInThreadExecution() throws Exception { final ListeningExecutorService executor = newDirectExecutorService(); final ThreadLocal threadLocalCount = @@ -168,7 +173,6 @@ public Integer call() { assertEquals(10, threadLocalCount.get().intValue()); } - public void testDirectExecutorServiceServiceTermination() throws Exception { final ExecutorService executor = newDirectExecutorService(); final CyclicBarrier barrier = new CyclicBarrier(2); @@ -187,19 +191,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,35 +219,25 @@ 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); @@ -259,15 +253,14 @@ public Void call() throws Exception { * 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(); + final 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 +269,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 +277,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 +290,6 @@ void awaitTimedWaiting(Thread thread) { case TIMED_WAITING: return; case TERMINATED: - default: throw new AssertionError(); } } @@ -311,11 +304,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 +331,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,7 +344,6 @@ public void run() {} verify(delegate).execute(task); } - public void testListeningDecorator_scheduleSuccess() throws Exception { final CountDownLatch completed = new CountDownLatch(1); ScheduledThreadPoolExecutor delegate = @@ -365,8 +354,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 +368,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 +385,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 +410,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 +418,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 +426,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 +465,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 +477,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 +498,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 +512,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 +528,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 +550,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)); + new ThreadPoolExecutor(1, 2, 3, SECONDS, new ArrayBlockingQueue(1)); assertNotNull(application.getExitingExecutorService(executor)); 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 +590,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,7 +601,6 @@ public void testGetExitingExecutorService_shutdownHookRegistered() throws Interr verify(executor).shutdown(); } - public void testGetExitingScheduledExecutorService_executorSetToUseDaemonThreads() { TestApplication application = new TestApplication(); ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); @@ -626,7 +608,7 @@ public void testGetExitingScheduledExecutorService_executorSetToUseDaemonThreads 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 +618,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(); @@ -669,7 +651,6 @@ public void run() { assertEquals(oldName, Thread.currentThread().getName()); } - public void testExecutors_nullCheck() throws Exception { new ClassSanityTester() .setDefault(RateLimiter.class, RateLimiter.create(1.0)) @@ -699,14 +680,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 +696,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 +709,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,7 +721,7 @@ 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); when(service.awaitTermination(HALF_SECOND_NANOS, NANOSECONDS)) 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..a73e127916c4 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,11 +16,14 @@ 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; @@ -35,7 +38,7 @@ 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); @@ -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); } @@ -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..8bb5a7cc8010 --- /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(emulated = true) +@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..3b4ac0383381 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,6 +19,8 @@ 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.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -33,16 +35,17 @@ 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 { @@ -79,11 +82,7 @@ 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() { @@ -263,18 +262,21 @@ 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); @@ -303,17 +305,16 @@ 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(); @@ -337,19 +338,12 @@ 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() { 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..be35f06ad917 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.Iterables; import com.google.common.collect.Lists; 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.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 { @@ -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,6 +142,20 @@ 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: @@ -143,7 +168,7 @@ public void testServiceStartupTimes_selfStartingServices() { 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); } @@ -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 { @@ -368,7 +364,7 @@ protected void doStop() { final 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); } @@ -388,7 +384,7 @@ protected void doStop() { }; final 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); } @@ -441,11 +437,41 @@ 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); @@ -500,7 +526,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 +545,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()); @@ -544,6 +566,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 +613,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,7 +628,6 @@ 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(); @@ -615,12 +636,12 @@ public void testTransitionRace() throws TimeoutException { } 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. */ @@ -675,4 +696,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..23c39af8effe 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(); @@ -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 { @@ -131,12 +118,7 @@ public void testSetFuture() throws Exception { // 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()); @@ -158,12 +140,7 @@ public void testSetFuture_genericsHierarchy() throws Exception { // 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..88d12176d8e7 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 @@ -18,6 +18,7 @@ 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.base.Stopwatch; import com.google.common.collect.Range; @@ -27,6 +28,7 @@ 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 +36,7 @@ * @author kevinb * @author Jens Nyman */ - +@NullUnmarked public class SimpleTimeLimiterTest extends TestCase { private static final long DELAY_MS = 50; @@ -109,11 +111,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 +125,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 +135,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 +150,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 +173,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 +195,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 +217,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 { @@ -279,7 +257,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..dadba8cc3ec5 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 @@ -37,12 +37,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 +52,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 +74,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 +104,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(); } @@ -129,7 +127,7 @@ public void testSizes() { 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 +135,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,7 +148,7 @@ 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())); 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..1fcbf03b280d 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 @@ -18,12 +18,14 @@ 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 +35,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() { 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..c8fc26da8afe 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,7 +29,7 @@ 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) @@ -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..e183f5b69f07 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,6 +17,7 @@ package com.google.common.util.concurrent; import static com.google.common.base.Preconditions.checkNotNull; +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; @@ -26,10 +27,10 @@ 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 +49,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 +60,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); @@ -77,9 +79,7 @@ public void tearDown() throws Exception { join(); if (uncaughtThrowable != null) { - throw (AssertionFailedError) - new AssertionFailedError("Uncaught throwable in " + getName()) - .initCause(uncaughtThrowable); + throw new AssertionError("Uncaught throwable in " + getName(), uncaughtThrowable); } } @@ -168,7 +168,7 @@ public void callAndAssertWaits(String methodName, Object conditionLikeObject) th * 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 +176,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 +188,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 +202,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 +274,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,7 +282,7 @@ private static class Response { Object getResult() { if (throwable != null) { - throw (AssertionFailedError) new AssertionFailedError().initCause(throwable); + throw new AssertionError(throwable); } return result; } 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..c29e8cb04f99 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,6 +17,7 @@ package com.google.common.util.concurrent; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.testing.NullPointerTester; import java.lang.Thread.UncaughtExceptionHandler; @@ -24,6 +25,7 @@ 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,7 +59,6 @@ public void setUp() { builder = new ThreadFactoryBuilder(); } - public void testThreadFactoryBuilder_defaults() throws InterruptedException { ThreadFactory threadFactory = builder.build(); Thread thread = threadFactory.newThread(monitoredRunnable); @@ -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,7 +174,6 @@ 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; 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..397c41d8f526 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,10 +19,12 @@ 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 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; @@ -33,8 +35,11 @@ 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}. */ +@NullMarked @GwtCompatible(emulated = true) public class TrustedListenableFutureTaskTest extends TestCase { @@ -54,11 +59,7 @@ 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(); } @@ -75,16 +76,13 @@ 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); @@ -125,17 +123,13 @@ 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); @@ -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 = + final 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..f488040b8d1d 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 @@ -21,9 +21,12 @@ import com.google.common.util.concurrent.UncaughtExceptionHandlers.Exiter; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; -/** @author Gregory Kick */ - +/** + * @author Gregory Kick + */ +@NullUnmarked public class UncaughtExceptionHandlersTest extends TestCase { private Runtime runtimeMock; 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..512de121e264 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,8 +18,10 @@ import static com.google.common.util.concurrent.InterruptionUtil.repeatedlyInterruptTestThread; import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; +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; @@ -29,9 +31,9 @@ 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; @@ -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); @@ -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(); 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..366dc00017f6 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 @@ -34,6 +34,7 @@ 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; @@ -48,13 +49,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"; @@ -469,6 +471,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); 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..68edc7b7691e 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,6 +19,8 @@ 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; @@ -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); @@ -188,7 +193,7 @@ 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; 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..548a45273a1b 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,6 +17,8 @@ package com.google.common.util.concurrent; import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; import java.util.Collection; import java.util.List; @@ -29,12 +31,14 @@ 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() { @@ -47,13 +51,13 @@ public void testSchedule() { 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 possiblyIgnoredError = 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); + testExecutor.schedule(Executors.callable(DO_NOTHING), 5, SECONDS); + mock.assertLastMethodCalled("scheduleCallable", 5, SECONDS); } public void testSchedule_repeating() { @@ -61,13 +65,12 @@ 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 { 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..2199c8ff9374 100644 --- a/android/guava/pom.xml +++ b/android/guava/pom.xml @@ -1,6 +1,7 @@ + 4.0.0 com.google.guava @@ -10,6 +11,7 @@ guava bundle Guava: Google Core Libraries for Java + https://github.com/google/guava Guava is a suite of core and expanded libraries that include utility classes, Google's collections, I/O classes, and @@ -19,7 +21,7 @@ com.google.guava failureaccess - 1.0.1 + 1.0.2 com.google.guava @@ -27,16 +29,8 @@ 9999.0-empty-to-avoid-conflict-with-guava - 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 + 5.1.8 bundle-manifest @@ -98,47 +107,18 @@ maven-source-plugin - - - maven-dependency-plugin - - - unpack-jdk-sources - generate-sources - unpack-dependencies - - srczip - ${project.build.directory}/jdk-sources - false - - **/module-info.java,**/java/io/FileDescriptor.java - - - - 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 +149,67 @@ - + 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/ ${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://docs.oracle.com/en/java/javase/21/docs/api/ https://errorprone.info/api/latest/ + 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/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..1aa84642ccc1 100644 --- a/android/guava/src/com/google/common/base/Absent.java +++ b/android/guava/src/com/google/common/base/Absent.java @@ -19,7 +19,7 @@ import com.google.common.annotations.GwtCompatible; 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 +61,7 @@ public T or(Supplier supplier) { } @Override - @NullableDecl - public T orNull() { + public @Nullable T orNull() { return null; } @@ -78,7 +77,7 @@ public Optional transform(Function function) { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return object == this; } 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..d8f5dc5f9757 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) { diff --git a/android/guava/src/com/google/common/base/CaseFormat.java b/android/guava/src/com/google/common/base/CaseFormat.java index 7b393ebd7e0f..eb0f6275be85 100644 --- a/android/guava/src/com/google/common/base/CaseFormat.java +++ b/android/guava/src/com/google/common/base/CaseFormat.java @@ -19,7 +19,7 @@ import com.google.common.annotations.GwtCompatible; 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 +29,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 +153,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,7 +184,7 @@ protected String doBackward(String s) { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof StringConverter) { StringConverter that = (StringConverter) object; return sourceFormat.equals(that.sourceFormat) && targetFormat.equals(that.targetFormat); diff --git a/android/guava/src/com/google/common/base/CharMatcher.java b/android/guava/src/com/google/common/base/CharMatcher.java index 7941883ec876..94a0d938987c 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; @@ -61,7 +63,6 @@ * @since 1.0 */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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. * @@ -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); @@ -904,8 +907,16 @@ 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 (even after devising a way for `ForPredicate`, which + * currently overrides it, to keep the null check that it inserts). + */ + @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 +976,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 +1019,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 +1116,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 +1232,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 +1289,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 +1363,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 +1373,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 +1389,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 +1405,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 +1421,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 +1437,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 +1453,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 +1472,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 +1488,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( @@ -1804,7 +1815,7 @@ public boolean matches(char c) { return predicate.apply(c); } - @SuppressWarnings("deprecation") // intentional; deprecation is for callers primarily + @Deprecated @Override public boolean apply(Character character) { return predicate.apply(checkNotNull(character)); diff --git a/android/guava/src/com/google/common/base/Charsets.java b/android/guava/src/com/google/common/base/Charsets.java index 7aebea826c78..aa371a5735b4 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 @@ -31,73 +33,54 @@ * @since 1.0 */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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..b6e8e5048e88 100644 --- a/android/guava/src/com/google/common/base/Converter.java +++ b/android/guava/src/com/google/common/base/Converter.java @@ -18,13 +18,14 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; -import com.google.errorprone.annotations.CanIgnoreReturnValue; +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} @@ -69,7 +70,7 @@ * create a "fake" converter for a unit test. It is unnecessary (and confusing) to mock * the {@code Converter} type using a mocking framework. *

  • 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 + *
  • Java 8+ users: you may prefer to pass two lambda expressions or method references to * the {@link #from from} factory method. * * @@ -114,7 +115,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 +143,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 +189,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 +202,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 +237,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 +253,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,7 +263,7 @@ 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 @@ -284,8 +277,6 @@ public boolean hasNext() { } @Override - @SuppressWarnings("nullness") // See code comments on convertAll and Converter.apply. - @CheckForNull public B next() { return convert(fromIterator.next()); } @@ -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); @@ -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); @@ -459,40 +446,26 @@ 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); } @@ -508,7 +481,7 @@ public final B apply(@CheckForNull A a) { * interchangeable. */ @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) { 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/ElementTypesAreNonnullByDefault.java b/android/guava/src/com/google/common/base/ElementTypesAreNonnullByDefault.java deleted file mode 100755 index 890e3a36062a..000000000000 --- a/android/guava/src/com/google/common/base/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.base; - -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/base/Enums.java b/android/guava/src/com/google/common/base/Enums.java index 449b7e3a95f7..4b185d29c2f8 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() {} @@ -48,7 +48,8 @@ private Enums() {} */ @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) { @@ -98,17 +99,19 @@ 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); + @GwtIncompatible + public static > Converter stringConverter(Class enumClass) { + return new StringConverter<>(enumClass); } + @GwtIncompatible private static final class StringConverter> extends Converter implements Serializable { @@ -129,7 +132,7 @@ protected String doBackward(T enumValue) { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof StringConverter) { StringConverter that = (StringConverter) object; return this.enumClass.equals(that.enumClass); diff --git a/android/guava/src/com/google/common/base/Equivalence.java b/android/guava/src/com/google/common/base/Equivalence.java index 9069ecf1a6cb..c8ae89bad0f6 100644 --- a/android/guava/src/com/google/common/base/Equivalence.java +++ b/android/guava/src/com/google/common/base/Equivalence.java @@ -19,8 +19,8 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.ForOverride; import java.io.Serializable; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * A strategy for determining whether two instances are considered equivalent, and for computing @@ -39,7 +39,6 @@ * 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. @@ -65,7 +64,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,7 +75,6 @@ public final boolean equivalent(@CheckForNull T a, @CheckForNull T b) { } /** - * * @since 10.0 (previously, subclasses would override equivalent()) */ @ForOverride @@ -99,7 +97,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; } @@ -150,10 +148,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); } /** @@ -177,10 +178,19 @@ public final Equivalence onResultOf(Function 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 +207,7 @@ public T get() { * equivalence. */ @Override - public boolean equals(@CheckForNull Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == this) { return true; } @@ -244,13 +254,15 @@ 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,28 +271,28 @@ 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; } diff --git a/android/guava/src/com/google/common/base/ExtraObjectsMethodsForWeb.java b/android/guava/src/com/google/common/base/ExtraObjectsMethodsForWeb.java index 677075522028..21cca2c109d6 100644 --- a/android/guava/src/com/google/common/base/ExtraObjectsMethodsForWeb.java +++ b/android/guava/src/com/google/common/base/ExtraObjectsMethodsForWeb.java @@ -21,5 +21,4 @@ * version. */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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..e6059dc280b6 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 @@ -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: + * + *

    {@code
    + * 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,8 +277,7 @@ interface FinalizerLoader { * * @throws SecurityException if we don't have the appropriate privileges */ - @NullableDecl - Class loadFinalizer(); + @Nullable Class loadFinalizer(); } /** @@ -242,8 +290,7 @@ static class SystemLoader implements FinalizerLoader { @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 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..ef0780eb380c 100644 --- a/android/guava/src/com/google/common/base/Function.java +++ b/android/guava/src/com/google/common/base/Function.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.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Determines an output value based on an input value; a pre-Java-8 version of {@link @@ -44,7 +43,7 @@ * @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: @@ -59,9 +58,8 @@ public interface Function { * @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 @@ -75,5 +73,5 @@ public interface Function { * disappear. It is best not to depend on it. */ @Override - boolean equals(@NullableDecl Object object); + boolean equals(@Nullable Object object); } diff --git a/android/guava/src/com/google/common/base/FunctionalEquivalence.java b/android/guava/src/com/google/common/base/FunctionalEquivalence.java index 228d34d04064..8d5a50b449e4 100644 --- a/android/guava/src/com/google/common/base/FunctionalEquivalence.java +++ b/android/guava/src/com/google/common/base/FunctionalEquivalence.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.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Equivalence applied on functional result. @@ -27,17 +26,16 @@ * @author Bob Lee * @since 10.0 */ -@Beta @GwtCompatible final class FunctionalEquivalence extends Equivalence implements Serializable { 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 +51,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..92d1cb09696c 100644 --- a/android/guava/src/com/google/common/base/Functions.java +++ b/android/guava/src/com/google/common/base/Functions.java @@ -14,13 +14,14 @@ 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 java.io.Serializable; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@code com.google.common.base.Function} instances; see that @@ -52,9 +53,9 @@ 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 + *

    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. + * for more important information about the Java 8+ transition. */ public static Function toStringFunction() { return ToStringFunction.INSTANCE; @@ -76,20 +77,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 +113,12 @@ 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 + *

    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. */ - public static Function forMap(Map map) { + public static Function forMap( + Map map) { return new FunctionForMapNoDefault<>(map); } @@ -121,7 +127,7 @@ 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 -> + *

    Java 8+ users: you can just write the lambda expression {@code k -> * map.getOrDefault(k, defaultValue)} instead. * * @param map source map that determines the function behavior @@ -129,12 +135,14 @@ public static Function forMap(Map map) { * @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 class FunctionForMapNoDefault< + K extends @Nullable Object, V extends @Nullable Object> + implements Function, Serializable { final Map map; FunctionForMapNoDefault(Map map) { @@ -142,14 +150,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); @@ -170,23 +180,28 @@ public String toString() { private static final long serialVersionUID = 0; } - private static class ForMapWithDefault implements Function, Serializable { + private static class ForMapWithDefault + 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); @@ -212,7 +227,7 @@ public String toString() { * 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)} + *

    Java 8+ users: use {@code g.compose(f)} or (probably clearer) {@code f.andThen(g)} * instead. * * @param g the second function to apply @@ -220,11 +235,14 @@ public String toString() { * @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 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; @@ -234,12 +252,13 @@ public FunctionComposition(Function g, Function 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); @@ -267,14 +286,18 @@ 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. + *

    Java 8+ users: use the method reference {@code predicate::test} instead. */ - 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 class PredicateFunction + implements Function, Serializable { private final Predicate predicate; private PredicateFunction(Predicate predicate) { @@ -282,12 +305,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); @@ -311,29 +334,32 @@ public String toString() { /** * Returns a function that ignores its input and always returns {@code value}. * - *

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

    Java 8+ users: use the lambda expression {@code o -> value} instead. * * @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 class ConstantFunction + implements Function<@Nullable Object, E>, Serializable { + @ParametricNullness private final E value; - public ConstantFunction(@NullableDecl E value) { + public 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); @@ -357,16 +383,20 @@ public String toString() { /** * 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. + *

    Java 8+ users: use the lambda expression {@code o -> supplier.get()} instead. * * @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 class SupplierFunction + implements Function, Serializable { private final Supplier supplier; @@ -375,14 +405,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; 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..4d6cfd48da94 --- /dev/null +++ b/android/guava/src/com/google/common/base/IgnoreJRERequirement.java @@ -0,0 +1,29 @@ +/* + * 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.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}) +@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..648d1c40638e --- /dev/null +++ b/android/guava/src/com/google/common/base/Internal.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + @SuppressWarnings({ + // We use this method only for cases in which we need to decompose to primitives. + "GoodTime-ApiWithNumericTimeUnit", + "GoodTime-DecomposeToPrimitive", + // We use this method only from within APIs that require a Duration. + "Java7ApiChecker", + }) + @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..f7791dba6e95 100644 --- a/android/guava/src/com/google/common/base/JdkPattern.java +++ b/android/guava/src/com/google/common/base/JdkPattern.java @@ -20,7 +20,6 @@ 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; diff --git a/android/guava/src/com/google/common/base/Joiner.java b/android/guava/src/com/google/common/base/Joiner.java index f74f02feba0a..7f95b591907f 100644 --- a/android/guava/src/com/google/common/base/Joiner.java +++ b/android/guava/src/com/google/common/base/Joiner.java @@ -15,17 +15,18 @@ 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 @@ -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..38d549663383 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; } @@ -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..5c99948ad47c --- 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 @@ -52,7 +50,7 @@ final class NullnessCasts { */ @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..114520c51d4b 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,7 +29,6 @@ * @since 2.0 */ @GwtCompatible -@ElementTypesAreNonnullByDefault public final class Objects extends ExtraObjectsMethodsForWeb { private Objects() {} @@ -47,10 +45,10 @@ 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) { + public static boolean equal(@Nullable Object a, @Nullable Object b) { return a == b || (a != null && a.equals(b)); } @@ -72,10 +70,10 @@ public static boolean equal(@CheckForNull Object a, @CheckForNull Object b) { *

    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) { + public static int hashCode(@Nullable Object @Nullable ... objects) { return Arrays.hashCode(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..1de6b59bbe85 100644 --- a/android/guava/src/com/google/common/base/Optional.java +++ b/android/guava/src/com/google/common/base/Optional.java @@ -16,13 +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.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 +51,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 @@ -82,7 +84,6 @@ */ @DoNotMock("Use Optional.of(value) or Optional.absent()") @GwtCompatible(serializable = true) -@ElementTypesAreNonnullByDefault public abstract class Optional implements Serializable { /** * Returns an {@code Optional} instance with no contained reference. @@ -103,7 +104,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 +114,66 @@ 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("Java7ApiChecker") + @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. + "Java7ApiChecker", + }) + // 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. + "Java7ApiChecker", + }) + // 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 +188,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 @@ -192,12 +249,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 +263,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 @@ -241,7 +296,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 +311,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 +326,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,7 +344,6 @@ public static Optional fromNullable(@CheckForNull T nullableReference) { * * @since 11.0 (generics widened in 13.0) */ - @Beta public static Iterable presentInstances( final Iterable> optionals) { checkNotNull(optionals); @@ -301,7 +355,7 @@ public Iterator iterator() { checkNotNull(optionals.iterator()); @Override - protected T computeNext() { + protected @Nullable T computeNext() { while (iterator.hasNext()) { Optional optional = iterator.next(); if (optional.isPresent()) { diff --git a/android/guava/src/com/google/common/base/PairwiseEquivalence.java b/android/guava/src/com/google/common/base/PairwiseEquivalence.java index cb7d784f016c..9720f774fed1 100644 --- a/android/guava/src/com/google/common/base/PairwiseEquivalence.java +++ b/android/guava/src/com/google/common/base/PairwiseEquivalence.java @@ -17,14 +17,14 @@ import com.google.common.annotations.GwtCompatible; 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 { +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 +52,10 @@ protected int doHash(Iterable iterable) { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof PairwiseEquivalence) { - PairwiseEquivalence that = (PairwiseEquivalence) object; + @SuppressWarnings("unchecked") + PairwiseEquivalence that = (PairwiseEquivalence) object; return this.elementEquivalence.equals(that.elementEquivalence); } 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..4c54555dc5a0 100644 --- a/android/guava/src/com/google/common/base/Platform.java +++ b/android/guava/src/com/google/common/base/Platform.java @@ -17,11 +17,8 @@ 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. @@ -30,31 +27,34 @@ */ @GwtCompatible(emulated = true) 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,7 +74,7 @@ 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; } @@ -96,10 +96,6 @@ private static PatternCompiler loadPatternCompiler() { 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 +107,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..4799af51e72a 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 @@ -137,7 +136,7 @@ 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)); } @@ -158,8 +157,8 @@ 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)); } @@ -172,8 +171,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 +184,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 +197,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,8 +211,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, @NullableDecl Object p1) { - if (!b) { + boolean expression, String errorMessageTemplate, @Nullable Object p1) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1)); } } @@ -226,8 +225,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 +239,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 +253,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,8 +267,8 @@ 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) { + boolean expression, String errorMessageTemplate, char p1, @Nullable Object p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -282,8 +281,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 +295,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 +309,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,8 +323,8 @@ 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) { + boolean expression, String errorMessageTemplate, int p1, @Nullable Object p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -338,8 +337,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 +351,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 +365,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,8 +379,8 @@ 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) { + boolean expression, String errorMessageTemplate, long p1, @Nullable Object p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -394,8 +393,8 @@ 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) { + boolean expression, String errorMessageTemplate, @Nullable Object p1, char p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -408,8 +407,8 @@ 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) { + boolean expression, String errorMessageTemplate, @Nullable Object p1, int p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -422,8 +421,8 @@ 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) { + boolean expression, String errorMessageTemplate, @Nullable Object p1, long p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -436,11 +435,12 @@ 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) { + 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(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -453,12 +453,12 @@ 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) { + boolean expression, + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2, + @Nullable Object p3) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2, p3)); } } @@ -471,13 +471,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, - @NullableDecl Object p4) { - if (!b) { + boolean expression, + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2, + @Nullable Object p3, + @Nullable Object p4) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2, p3, p4)); } } @@ -506,7 +506,7 @@ 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)); } @@ -529,8 +529,16 @@ 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)); } @@ -544,8 +552,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 +566,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 +580,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,8 +595,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, @NullableDecl Object p1) { - if (!b) { + boolean expression, String errorMessageTemplate, @Nullable Object p1) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1)); } } @@ -601,9 +609,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 +623,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 +637,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,8 +652,8 @@ 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) { + boolean expression, String errorMessageTemplate, char p1, @Nullable Object p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -661,9 +666,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 +680,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 +694,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,8 +709,8 @@ 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) { + boolean expression, String errorMessageTemplate, int p1, @Nullable Object p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -721,9 +723,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 +737,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 +751,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,8 +766,8 @@ 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) { + boolean expression, String errorMessageTemplate, long p1, @Nullable Object p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -782,8 +781,8 @@ 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) { + boolean expression, String errorMessageTemplate, @Nullable Object p1, char p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -797,8 +796,8 @@ 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) { + boolean expression, String errorMessageTemplate, @Nullable Object p1, int p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -812,8 +811,8 @@ 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) { + boolean expression, String errorMessageTemplate, @Nullable Object p1, long p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -827,11 +826,8 @@ 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) { + boolean expression, String errorMessageTemplate, @Nullable Object p1, @Nullable Object p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -845,12 +841,12 @@ 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) { + boolean expression, + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2, + @Nullable Object p3) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2, p3)); } } @@ -864,17 +860,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) { + boolean expression, + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2, + @Nullable Object p3, + @Nullable Object p4) { + if (!expression) { throw new IllegalStateException(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 +894,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,9 +912,7 @@ 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)); } @@ -928,11 +935,10 @@ 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)); } @@ -947,13 +953,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 +968,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 +983,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 +998,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) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, @Nullable Object p1) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1)); } - return obj; + return reference; } /** @@ -1015,13 +1014,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 +1030,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 +1046,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 +1062,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) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, char p1, @Nullable Object p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1086,13 +1078,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 +1094,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 +1110,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 +1126,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) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, int p1, @Nullable Object p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1157,13 +1142,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 +1158,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 +1174,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 +1190,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) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, long p1, @Nullable Object p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1228,16 +1206,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) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, @Nullable Object p1, char p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1248,16 +1222,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) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, @Nullable Object p1, int p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1268,16 +1238,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) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, @Nullable Object p1, long p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1288,16 +1254,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) { + public static T checkNotNull( + @Nullable T reference, + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1308,17 +1273,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) { + public static T checkNotNull( + @Nullable T reference, + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2, + @Nullable Object p3) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2, p3)); } - return obj; + return reference; } /** @@ -1329,18 +1293,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) { + 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(lenientFormat(errorMessageTemplate, p1, p2, p3, p4)); } - return obj; + return reference; } /* @@ -1396,7 +1359,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 +1367,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) { @@ -1441,7 +1404,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 +1412,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..54438f36419a 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,7 +60,6 @@ public interface Predicate { * @throws NullPointerException if {@code input} is null and this predicate does not accept null * arguments */ - @CanIgnoreReturnValue boolean apply(@ParametricNullness T input); /** @@ -77,5 +73,5 @@ public interface Predicate { * predicates are known not to be interchangeable. */ @Override - boolean equals(@CheckForNull Object object); + boolean equals(@Nullable Object object); } diff --git a/android/guava/src/com/google/common/base/Predicates.java b/android/guava/src/com/google/common/base/Predicates.java index 9c8f1e5ea256..be84124c37d4 100644 --- a/android/guava/src/com/google/common/base/Predicates.java +++ b/android/guava/src/com/google/common/base/Predicates.java @@ -16,16 +16,16 @@ 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.regex.Pattern; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@code Predicate} instances. @@ -47,13 +47,13 @@ private Predicates() {} /** Returns a predicate that always evaluates to {@code true}. */ @GwtCompatible(serializable = true) - public static Predicate alwaysTrue() { + 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() { + public static Predicate alwaysFalse() { return ObjectPredicate.ALWAYS_FALSE.withNarrowedType(); } @@ -62,7 +62,7 @@ public static Predicate alwaysFalse() { * null. */ @GwtCompatible(serializable = true) - public static Predicate isNull() { + public static Predicate isNull() { return ObjectPredicate.IS_NULL.withNarrowedType(); } @@ -71,7 +71,7 @@ public static Predicate isNull() { * null. */ @GwtCompatible(serializable = true) - public static Predicate notNull() { + public static Predicate notNull() { return ObjectPredicate.NOT_NULL.withNarrowedType(); } @@ -79,8 +79,8 @@ public static Predicate notNull() { * Returns a predicate that evaluates to {@code true} if the given predicate evaluates to {@code * false}. */ - public static Predicate not(Predicate predicate) { - return new NotPredicate(predicate); + public static Predicate not(Predicate predicate) { + return new NotPredicate<>(predicate); } /** @@ -90,8 +90,9 @@ public static Predicate not(Predicate predicate) { * 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}. */ - public static Predicate and(Iterable> components) { - return new AndPredicate(defensiveCopy(components)); + public static Predicate and( + Iterable> components) { + return new AndPredicate<>(defensiveCopy(components)); } /** @@ -102,7 +103,7 @@ public static Predicate and(Iterable> comp * returned predicate will always evaluate to {@code true}. */ @SafeVarargs - public static Predicate and(Predicate... components) { + public static Predicate and(Predicate... components) { return new AndPredicate(defensiveCopy(components)); } @@ -111,8 +112,9 @@ public static Predicate and(Predicate... components) { * true}. The components are evaluated in order, and evaluation will be "short-circuited" as soon * as a false predicate is found. */ - 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))); } /** @@ -122,8 +124,9 @@ public static Predicate and(Predicate first, Predicate Predicate or(Iterable> components) { - return new OrPredicate(defensiveCopy(components)); + public static Predicate or( + Iterable> components) { + return new OrPredicate<>(defensiveCopy(components)); } /** @@ -134,7 +137,7 @@ public static Predicate or(Iterable> compo * returned predicate will always evaluate to {@code false}. */ @SafeVarargs - public static Predicate or(Predicate... components) { + public static Predicate or(Predicate... components) { return new OrPredicate(defensiveCopy(components)); } @@ -143,16 +146,19 @@ public static Predicate or(Predicate... components) { * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited" * as soon as a true predicate is found. */ - 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. */ - 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(); } /** @@ -169,8 +175,8 @@ public static Predicate equalTo(@NullableDecl T target) { * instances {@code Lists.newArrayList(1)} and {@code Arrays.asList(1)}. */ @GwtIncompatible // Class.isInstance - public static Predicate instanceOf(Class clazz) { - return new InstanceOfPredicate(clazz); + public static Predicate instanceOf(Class clazz) { + return new InstanceOfPredicate<>(clazz); } /** @@ -187,8 +193,8 @@ public static Predicate instanceOf(Class clazz) { * * @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); } @@ -204,8 +210,8 @@ public static Predicate> subtypeOf(Class clazz) { * * @param target the collection that may contain the function input */ - public static Predicate in(Collection target) { - return new InPredicate(target); + public static Predicate in(Collection target) { + return new InPredicate<>(target); } /** @@ -214,7 +220,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); } @@ -247,11 +253,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() */ + 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 +268,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 +282,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 +296,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 +312,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 class NotPredicate + implements Predicate, Serializable { final Predicate predicate; NotPredicate(Predicate predicate) { @@ -312,7 +329,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 +339,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); @@ -338,8 +355,11 @@ public String toString() { private static final long serialVersionUID = 0; } - /** @see Predicates#and(Iterable) */ - private static class AndPredicate implements Predicate, Serializable { + /** + * @see Predicates#and(Iterable) + */ + private static class AndPredicate + implements Predicate, Serializable { private final List> components; private AndPredicate(List> components) { @@ -347,7 +367,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 +384,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); @@ -380,8 +400,11 @@ public String toString() { private static final long serialVersionUID = 0; } - /** @see Predicates#or(Iterable) */ - private static class OrPredicate implements Predicate, Serializable { + /** + * @see Predicates#or(Iterable) + */ + private static class OrPredicate + implements Predicate, Serializable { private final List> components; private OrPredicate(List> components) { @@ -389,7 +412,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 +429,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); @@ -435,17 +458,19 @@ 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 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 +479,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; @@ -468,11 +493,19 @@ public String toString() { } 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 class InstanceOfPredicate + implements Predicate, Serializable { private final Class clazz; private InstanceOfPredicate(Class clazz) { @@ -480,7 +513,7 @@ private InstanceOfPredicate(Class clazz) { } @Override - public boolean apply(@NullableDecl Object o) { + public boolean apply(@ParametricNullness T o) { return clazz.isInstance(o); } @@ -490,9 +523,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,10 +536,13 @@ public String toString() { return "Predicates.instanceOf(" + clazz.getName() + ")"; } - private static final long serialVersionUID = 0; + @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 final Class clazz; @@ -526,7 +562,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; @@ -542,8 +578,11 @@ public String toString() { private static final long serialVersionUID = 0; } - /** @see Predicates#in(Collection) */ - private static class InPredicate implements Predicate, Serializable { + /** + * @see Predicates#in(Collection) + */ + private static class InPredicate + implements Predicate, Serializable { private final Collection target; private InPredicate(Collection target) { @@ -551,7 +590,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 +599,7 @@ public boolean apply(@NullableDecl T t) { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof InPredicate) { InPredicate that = (InPredicate) obj; return target.equals(that.target); @@ -581,8 +620,11 @@ public String toString() { 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 class CompositionPredicate + implements Predicate, Serializable { final Predicate p; final Function f; @@ -592,12 +634,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); @@ -619,7 +661,9 @@ public String toString() { 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; @@ -642,7 +686,7 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof ContainsPatternPredicate) { ContainsPatternPredicate that = (ContainsPatternPredicate) obj; @@ -667,7 +711,9 @@ public String toString() { 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 { @@ -683,7 +729,7 @@ public String toString() { 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,7 +740,7 @@ 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)); } diff --git a/android/guava/src/com/google/common/base/Present.java b/android/guava/src/com/google/common/base/Present.java index d33eb8e38567..97bf8ce14ad5 100644 --- a/android/guava/src/com/google/common/base/Present.java +++ b/android/guava/src/com/google/common/base/Present.java @@ -19,7 +19,7 @@ import com.google.common.annotations.GwtCompatible; 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,14 +70,14 @@ 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) { + public boolean equals(@Nullable Object object) { if (object instanceof Present) { Present other = (Present) object; return reference.equals(other.reference); diff --git a/android/guava/src/com/google/common/base/SmallCharMatcher.java b/android/guava/src/com/google/common/base/SmallCharMatcher.java index f0e801b67118..1e565c858b96 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; diff --git a/android/guava/src/com/google/common/base/Splitter.java b/android/guava/src/com/google/common/base/Splitter.java index bde2e0ec42f8..e6abafa79da3 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 @@ -98,7 +99,6 @@ * @since 1.0 */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault public final class Splitter { private final CharMatcher trimmer; private final boolean omitEmptyStrings; @@ -214,10 +214,11 @@ 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(final CommonPattern separatorPattern) { checkArgument( !separatorPattern.matcher("").matches(), "The pattern may not match the empty string: %s", @@ -257,7 +258,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)); } /** @@ -329,7 +330,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"]}. * @@ -423,13 +424,29 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + // 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 +457,6 @@ public MapSplitter withKeyValueSeparator(String separator) { * * @since 14.0 */ - @Beta public MapSplitter withKeyValueSeparator(char separator) { return withKeyValueSeparator(on(separator)); } @@ -464,7 +480,6 @@ public MapSplitter withKeyValueSeparator(char separator) { * * @since 10.0 */ - @Beta public MapSplitter withKeyValueSeparator(Splitter keyValueSplitter) { return new MapSplitter(this, keyValueSplitter); } @@ -477,7 +492,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; @@ -549,9 +563,8 @@ protected SplittingIterator(Splitter splitter, CharSequence toSplit) { 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..29f62a28eff2 100644 --- a/android/guava/src/com/google/common/base/StandardSystemProperty.java +++ b/android/guava/src/com/google/common/base/StandardSystemProperty.java @@ -15,7 +15,8 @@ package com.google.common.base; import com.google.common.annotations.GwtIncompatible; -import javax.annotation.CheckForNull; +import com.google.common.annotations.J2ktIncompatible; +import org.jspecify.annotations.Nullable; /** * Represents a {@linkplain System#getProperties() standard system property}. @@ -23,8 +24,8 @@ * @author Kurt Alfred Kluever * @since 15.0 */ +@J2ktIncompatible @GwtIncompatible // java.lang.System#getProperty -@ElementTypesAreNonnullByDefault public enum StandardSystemProperty { /** Java Runtime Environment version. */ @@ -125,7 +126,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 +154,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..48d6250a0411 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 {@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. * *

    Use this class instead of direct calls to {@link System#nanoTime} for two reasons: * @@ -49,6 +52,12 @@ * 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
    @@ -56,7 +65,7 @@
      * doSomething();
      * stopwatch.stop(); // optional
      *
    - * long millis = stopwatch.elapsed(MILLISECONDS);
    + * Duration duration = stopwatch.elapsed();
      *
      * log.info("time: " + stopwatch); // formatted string like "12.3 ms"
      * }
    @@ -77,7 +86,7 @@ * Stopwatch.createStarted( * new Ticker() { * public long read() { - * return android.os.SystemClock.elapsedRealtimeNanos(); + * return android.os.SystemClock.elapsedRealtimeNanos(); // requires API Level 17 * } * }); * } @@ -85,9 +94,8 @@ * @author Kevin Bourrillion * @since 10.0 */ -@GwtCompatible +@GwtCompatible(emulated = true) @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 {@code Duration} instance. * * @since 14.0 (since 10.0 as {@code elapsedTime()}) */ @@ -205,6 +217,27 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + // 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 +288,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..8cd4e27e670a 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); } @@ -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,7 +258,7 @@ 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) { @@ -300,7 +298,7 @@ public static String lenientFormat( return builder.toString(); } - private static String lenientToString(@CheckForNull Object o) { + private static String lenientToString(@Nullable Object o) { if (o == null) { return "null"; } diff --git a/android/guava/src/com/google/common/base/Supplier.java b/android/guava/src/com/google/common/base/Supplier.java index f9e1e34f17a0..85a05229aa0a 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,6 @@ public interface Supplier { * * @return an instance of the appropriate type */ - @CanIgnoreReturnValue @ParametricNullness T get(); } diff --git a/android/guava/src/com/google/common/base/Suppliers.java b/android/guava/src/com/google/common/base/Suppliers.java index da1490da2d7c..e99f73ec450a 100644 --- a/android/guava/src/com/google/common/base/Suppliers.java +++ b/android/guava/src/com/google/common/base/Suppliers.java @@ -14,14 +14,21 @@ 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.concurrent.TimeUnit; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Useful suppliers. @@ -32,7 +39,7 @@ * @author Harry Heymann * @since 2.0 */ -@GwtCompatible +@GwtCompatible(emulated = true) public final class Suppliers { private Suppliers() {} @@ -42,11 +49,13 @@ 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 class SupplierComposition + implements Supplier, Serializable { final Function function; final Supplier supplier; @@ -56,12 +65,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); @@ -90,7 +100,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 +108,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 +119,27 @@ public static Supplier memoize(Supplier delegate) { } @VisibleForTesting - static class MemoizingSupplier implements Supplier, Serializable { + static 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 +148,8 @@ public T get() { } } } - return value; + // This is safe because we checked `initialized`. + return uncheckedCastNullableTToT(value); } @Override @@ -143,44 +159,60 @@ public String toString() { + ")"; } + @GwtIncompatible // serialization + @J2ktIncompatible // serialization + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + lock = new Object(); + } + 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; + static 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 +238,68 @@ 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 + @SuppressWarnings("Java7ApiChecker") // no more dangerous that wherever the user got the 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 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 +308,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 +322,8 @@ public T get() { } } } - return value; + // This is safe because we checked `expirationNanos`. + return uncheckedCastNullableTToT(value); } @Override @@ -260,28 +333,38 @@ public String toString() { return "Suppliers.memoizeWithExpiration(" + delegate + ", " + durationNanos + ", NANOS)"; } + @GwtIncompatible // serialization + @J2ktIncompatible // serialization + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + lock = new Object(); + } + 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); + public static Supplier ofInstance( + @ParametricNullness T instance) { + return new SupplierOfInstance<>(instance); } - private static class SupplierOfInstance implements Supplier, Serializable { - @NullableDecl final T instance; + private static 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); @@ -306,11 +389,15 @@ public String toString() { * 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 class ThreadSafeSupplier + implements Supplier, Serializable { final Supplier delegate; ThreadSafeSupplier(Supplier delegate) { @@ -318,6 +405,7 @@ private static class ThreadSafeSupplier implements Supplier, Serializable } @Override + @ParametricNullness public T get() { synchronized (delegate) { return delegate.get(); @@ -336,24 +424,24 @@ public String toString() { * 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. + *

    Java 8+ users: use the method reference {@code Supplier::get} instead. * * @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..319828d8990b 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}. @@ -46,7 +46,6 @@ * @since 1.0 */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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..6c4797670f9f 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 public final class Utf8 { /** * Returns the number of bytes in the UTF-8-encoded form of {@code sequence}. For a string, this @@ -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) { diff --git a/android/guava/src/com/google/common/base/Verify.java b/android/guava/src/com/google/common/base/Verify.java index 843a77fe91f0..5ff06d11fead 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 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..bb5596ff58d5 100644 --- a/android/guava/src/com/google/common/cache/AbstractCache.java +++ b/android/guava/src/com/google/common/cache/AbstractCache.java @@ -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,8 +60,12 @@ 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) { + public ImmutableMap getAllPresent(Iterable keys) { Map result = Maps.newLinkedHashMap(); for (Object key : keys) { if (!result.containsKey(key)) { @@ -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..cc8df3faafef 100644 --- a/android/guava/src/com/google/common/cache/AbstractLoadingCache.java +++ b/android/guava/src/com/google/common/cache/AbstractLoadingCache.java @@ -18,6 +18,7 @@ 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.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 { 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..957cbf04a928 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: @@ -71,7 +99,7 @@ *
  • 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: @@ -79,7 +107,7 @@ *

    {@code
      * LoadingCache graphs = CacheBuilder.newBuilder()
      *     .maximumSize(10000)
    - *     .expireAfterWrite(10, TimeUnit.MINUTES)
    + *     .expireAfterWrite(Duration.ofMinutes(10))
      *     .removalListener(MY_LISTENER)
      *     .build(
      *         new CacheLoader() {
    @@ -105,13 +133,11 @@
      *         });
      * }
    * - *

    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 +145,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,11 +182,11 @@ * 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 @@ -171,10 +196,10 @@ 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 +229,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 +273,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 +286,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 +317,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 +327,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 +339,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 +349,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 +364,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 +385,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 +408,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 +454,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 +490,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 +532,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 +570,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 +623,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 +657,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 +683,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 +713,49 @@ 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 + "Java7ApiChecker", + }) + @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 */ - @SuppressWarnings("GoodTime") // should accept a java.time.Duration + @SuppressWarnings("GoodTime") // should accept a Duration + @CanIgnoreReturnValue public CacheBuilder expireAfterWrite(long duration, TimeUnit unit) { checkState( expireAfterWriteNanos == UNSET_INT, @@ -684,6 +771,44 @@ 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 + "Java7ApiChecker", + }) + @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,6 +826,9 @@ 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 @@ -708,7 +836,8 @@ long getExpireAfterWriteNanos() { * @throws IllegalArgumentException if {@code duration} is negative * @throws IllegalStateException if {@link #expireAfterAccess} was already set */ - @SuppressWarnings("GoodTime") // should accept a java.time.Duration + @SuppressWarnings("GoodTime") // should accept a Duration + @CanIgnoreReturnValue public CacheBuilder expireAfterAccess(long duration, TimeUnit unit) { checkState( expireAfterAccessNanos == UNSET_INT, @@ -738,12 +867,56 @@ 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 + "Java7ApiChecker", + }) + @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 @@ -753,7 +926,8 @@ long getExpireAfterAccessNanos() { * @since 11.0 */ @GwtIncompatible // To be supported (synchronously). - @SuppressWarnings("GoodTime") // should accept a java.time.Duration + @SuppressWarnings("GoodTime") // should accept a Duration + @CanIgnoreReturnValue public CacheBuilder refreshAfterWrite(long duration, TimeUnit unit) { checkNotNull(unit); checkState(refreshNanos == UNSET_INT, "refresh was already set to %s ns", refreshNanos); @@ -777,6 +951,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 +972,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 +1012,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 +1038,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 +1056,6 @@ public LoadingCache build( * @return a cache having the requested features * @since 11.0 */ - @CheckReturnValue public Cache build() { checkWeightWithWeigher(); checkNonLoadingCache(); @@ -902,7 +1074,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 +1123,27 @@ 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 + "Java7ApiChecker", + }) + @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..800058fb5a28 100644 --- a/android/guava/src/com/google/common/cache/CacheBuilderSpec.java +++ b/android/guava/src/com/google/common/cache/CacheBuilderSpec.java @@ -15,6 +15,11 @@ 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; @@ -27,7 +32,7 @@ import java.util.List; import java.util.Locale; 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; @@ -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; } @@ -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) { @@ -321,7 +330,7 @@ static class InitialCapacityParser extends IntegerParser { 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; } @@ -331,9 +340,10 @@ protected void parseInteger(CacheBuilderSpec spec, int value) { static 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; } } @@ -343,8 +353,9 @@ static 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; } } @@ -355,7 +366,7 @@ static class ConcurrencyLevelParser extends IntegerParser { 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; } @@ -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; @@ -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); @@ -399,7 +410,7 @@ public void parse(CacheBuilderSpec spec, String key, @NullableDecl String value) static 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( diff --git a/android/guava/src/com/google/common/cache/CacheLoader.java b/android/guava/src/com/google/common/cache/CacheLoader.java index aa1da922b90b..35e00b146607 100644 --- a/android/guava/src/com/google/common/cache/CacheLoader.java +++ b/android/guava/src/com/google/common/cache/CacheLoader.java @@ -15,18 +15,16 @@ 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.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; /** @@ -98,7 +96,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 +132,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,13 +146,14 @@ 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 @@ -181,7 +181,6 @@ public V load(K key) { * * @since 17.0 */ - @CheckReturnValue @GwtIncompatible // Executor + Futures public static CacheLoader asyncReloading( final CacheLoader loader, final Executor executor) { @@ -194,15 +193,9 @@ public V load(K key) throws Exception { } @Override - public ListenableFuture reload(final K key, final V oldValue) throws Exception { + public ListenableFuture reload(final K key, final 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; } diff --git a/android/guava/src/com/google/common/cache/CacheStats.java b/android/guava/src/com/google/common/cache/CacheStats.java index e0c39c3d7e67..094cc1c265b3 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.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. @@ -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))); } /** @@ -276,7 +277,7 @@ public int hashCode() { } @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..ea6a7b733dec --- /dev/null +++ b/android/guava/src/com/google/common/cache/IgnoreJRERequirement.java @@ -0,0 +1,29 @@ +/* + * 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.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}) +@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..40bcfb68591c 100644 --- a/android/guava/src/com/google/common/cache/LocalCache.java +++ b/android/guava/src/com/google/common/cache/LocalCache.java @@ -18,16 +18,19 @@ 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.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 +39,25 @@ 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 +68,9 @@ 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.Map; -import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Queue; import java.util.Set; @@ -75,13 +78,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,8 +96,12 @@ * @author Bob Lee ({@code com.google.common.collect.MapMaker}) * @author Doug Lea ({@code ConcurrentHashMap}) */ -@SuppressWarnings("GoodTime") // lots of violations (nanosecond math) +@SuppressWarnings({ + "GoodTime", // lots of violations (nanosecond math) + "nullness", // too much trouble for the payoff +}) @GwtCompatible(emulated = true) +@NullUnmarked // TODO(cpovirk): Annotate for nullness. class LocalCache extends AbstractMap implements ConcurrentMap { /* @@ -226,14 +233,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 +257,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 +277,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 +444,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 +469,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 +487,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 +506,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 +531,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 +549,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 +602,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 +649,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 +667,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 +675,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 +704,7 @@ ValueReference copyFor( static final ValueReference UNSET = new ValueReference() { @Override - public Object get() { + public @Nullable Object get() { return null; } @@ -685,14 +714,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 +737,7 @@ public boolean isActive() { } @Override - public Object waitForValue() { + public @Nullable Object waitForValue() { return null; } @@ -726,7 +755,7 @@ private enum NullEntry implements ReferenceEntry { INSTANCE; @Override - public ValueReference getValueReference() { + public @Nullable ValueReference getValueReference() { return null; } @@ -734,7 +763,7 @@ public ValueReference getValueReference() { public void setValueReference(ValueReference valueReference) {} @Override - public ReferenceEntry getNext() { + public @Nullable ReferenceEntry getNext() { return null; } @@ -744,7 +773,7 @@ public int getHash() { } @Override - public Object getKey() { + public @Nullable Object getKey() { return null; } @@ -897,12 +926,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; } @@ -935,7 +964,7 @@ static Queue discardingQueue() { 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 +978,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 +1003,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 +1049,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 +1095,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); } @@ -1153,7 +1182,7 @@ 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) { + WeakEntry(ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { super(key, queue); this.hash = hash; this.next = next; @@ -1236,7 +1265,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 +1290,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 +1336,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 +1383,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); } @@ -1660,7 +1687,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 +1718,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 +1761,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 +1840,7 @@ void processPendingNotifications() { @SuppressWarnings("unchecked") final Segment[] newSegmentArray(int ssize) { - return new Segment[ssize]; + return (Segment[]) new Segment[ssize]; } // Inner Classes @@ -1883,7 +1909,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 +1918,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 +1966,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 +1992,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 +2001,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 +2016,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 +2037,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 +2075,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(); @@ -2202,15 +2222,12 @@ ListenableFuture loadAsync( CacheLoader loader) { final 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 +2235,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,8 +2283,8 @@ 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) { + @CanIgnoreReturnValue + @Nullable V refresh(K key, int hash, CacheLoader loader, boolean checkTime) { final LoadingValueReference loadingValueReference = insertLoadingValueReference(key, hash, checkTime); if (loadingValueReference == null) { @@ -2288,8 +2306,7 @@ 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( + @Nullable LoadingValueReference insertLoadingValueReference( final K key, final int hash, boolean checkTime) { ReferenceEntry e = null; lock(); @@ -2476,7 +2493,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 +2535,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 +2597,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 +2617,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 +2697,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 +2903,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 +2957,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 +3049,7 @@ boolean remove(Object key, int hash, Object value) { } } + @CanIgnoreReturnValue boolean storeLoadedValue( K key, int hash, LoadingValueReference oldValueReference, V newValue) { lock(); @@ -3135,11 +3149,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 +3170,7 @@ ReferenceEntry removeValueFromChain( } @GuardedBy("this") - @NullableDecl - ReferenceEntry removeEntryFromChain( + @Nullable ReferenceEntry removeEntryFromChain( ReferenceEntry first, ReferenceEntry entry) { int newCount = count; ReferenceEntry newFirst = entry.getNext(); @@ -3188,6 +3200,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 +3236,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 +3274,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 +3316,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 +3408,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 +3432,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 +3466,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 +3515,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 +3585,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 +3603,7 @@ public ReferenceEntry poll() { @Override @SuppressWarnings("unchecked") + @CanIgnoreReturnValue public boolean remove(Object o) { ReferenceEntry e = (ReferenceEntry) o; ReferenceEntry previous = e.getPreviousInWriteQueue(); @@ -3633,7 +3654,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 +3725,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 +3743,7 @@ public ReferenceEntry poll() { @Override @SuppressWarnings("unchecked") + @CanIgnoreReturnValue public boolean remove(Object o) { ReferenceEntry e = (ReferenceEntry) o; ReferenceEntry previous = e.getPreviousInAccessQueue(); @@ -3772,7 +3794,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 +3823,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 +3845,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 +3856,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 +3866,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 +3883,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 +3897,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,7 +3912,7 @@ ImmutableMap getAllPresent(Iterable keys) { } globalStatsCounter.recordHits(hits); globalStatsCounter.recordMisses(misses); - return ImmutableMap.copyOf(result); + return result.buildKeepingLast(); } ImmutableMap getAll(Iterable keys) throws ExecutionException { @@ -3916,7 +3937,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 +3964,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 +4027,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 +4042,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 +4052,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; @@ -4071,8 +4091,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 +4101,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 +4115,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 +4125,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 +4135,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 +4147,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 +4170,7 @@ void invalidateAll(Iterable keys) { } } - @RetainedWith @NullableDecl Set keySet; + @LazyInit @RetainedWith @Nullable Set keySet; @Override public Set keySet() { @@ -4154,7 +4179,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 +4188,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 +4204,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 +4349,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 +4400,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 +4445,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> { @@ -4512,10 +4504,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 +4563,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); } @@ -4620,7 +4612,7 @@ static final class LoadingSerializationProxy extends ManualSerializationPr implements LoadingCache, Serializable { private static final long serialVersionUID = 1; - @NullableDecl transient LoadingCache autoDelegate; + transient @Nullable LoadingCache autoDelegate; LoadingSerializationProxy(LocalCache cache) { super(cache); @@ -4648,7 +4640,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 +4658,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,8 +4668,7 @@ private LocalManualCache(LocalCache localCache) { // Cache methods @Override - @NullableDecl - public V getIfPresent(Object key) { + public @Nullable V getIfPresent(Object key) { return localCache.getIfPresent(key); } @@ -4757,6 +4748,10 @@ public void cleanUp() { Object writeReplace() { return new ManualSerializationProxy<>(localCache); } + + private void readObject(ObjectInputStream in) throws InvalidObjectException { + throw new InvalidObjectException("Use ManualSerializationProxy"); + } } static class LocalLoadingCache extends LocalManualCache @@ -4764,7 +4759,7 @@ static class LocalLoadingCache extends LocalManualCache LocalLoadingCache( CacheBuilder builder, CacheLoader loader) { - super(new LocalCache(builder, checkNotNull(loader))); + super(new LocalCache<>(builder, checkNotNull(loader))); } // LoadingCache methods @@ -4774,6 +4769,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 { @@ -4806,5 +4802,9 @@ public final V apply(K key) { 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..e5da7c8b772a 100644 --- a/android/guava/src/com/google/common/cache/LongAddables.java +++ b/android/guava/src/com/google/common/cache/LongAddables.java @@ -30,7 +30,8 @@ final class LongAddables { 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/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..abf1ad7b7caf 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. 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..b7f6107ba569 100644 --- a/android/guava/src/com/google/common/cache/RemovalNotification.java +++ b/android/guava/src/com/google/common/cache/RemovalNotification.java @@ -18,7 +18,7 @@ import com.google.common.annotations.GwtCompatible; 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 +32,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 +44,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); } 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..23222c0f0e7e 100644 --- a/android/guava/src/com/google/common/collect/AbstractBiMap.java +++ b/android/guava/src/com/google/common/collect/AbstractBiMap.java @@ -18,12 +18,15 @@ 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.annotations.J2ktIncompatible; import com.google.common.base.Objects; 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; @@ -34,7 +37,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; /** * A general-purpose bimap implementation using any two backing {@code Map} instances. @@ -46,11 +49,15 @@ * @author Mike Bostock */ @GwtCompatible(emulated = true) -abstract class AbstractBiMap extends ForwardingMap - implements BiMap, Serializable { +abstract class AbstractBiMap + extends ForwardingMap implements BiMap, Serializable { - @NullableDecl private transient Map delegate; - @RetainedWith @NullableDecl transient AbstractBiMap inverse; + @SuppressWarnings("nullness:initialization.field.uninitialized") // For J2KT (lateinit) + private transient Map delegate; + + @SuppressWarnings("nullness:initialization.field.uninitialized") // For J2KT (lateinit) + @RetainedWith + transient AbstractBiMap inverse; /** Package-private constructor for creating a map-backed bimap. */ AbstractBiMap(Map forward, Map backward) { @@ -70,13 +77,15 @@ 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; } @@ -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,17 +122,18 @@ 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); @@ -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() { @@ -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() { @@ -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() { @@ -313,7 +331,7 @@ public V setValue(V value) { Iterator> entrySetIterator() { final 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); @@ -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 { + static class Inverse + extends AbstractBiMap { Inverse(Map backward, AbstractBiMap forward) { super(backward, forward); } @@ -423,38 +448,47 @@ static class Inverse extends AbstractBiMap { */ @Override - K checkKey(K key) { + @ParametricNullness + K checkKey(@ParametricNullness K key) { return inverse.checkValue(key); } @Override - V checkValue(V value) { + @ParametricNullness + V checkValue(@ParametricNullness V value) { return inverse.checkKey(value); } - /** @serialData the forward bimap */ + /** + * @serialData the forward bimap + */ @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(inverse()); } @GwtIncompatible // java.io.ObjectInputStream + @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. + @J2ktIncompatible private static final long serialVersionUID = 0; } @GwtIncompatible // Not needed in emulated source. + @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..452e260ffbbc 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 @@ -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..d68a2ed7c5a8 100644 --- a/android/guava/src/com/google/common/collect/AbstractListMultimap.java +++ b/android/guava/src/com/google/common/collect/AbstractListMultimap.java @@ -16,13 +16,15 @@ 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.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 +35,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. * @@ -49,16 +51,17 @@ protected AbstractListMultimap(Map> map) { @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 +75,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 +88,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 +101,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 +114,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,7 +136,7 @@ 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); } diff --git a/android/guava/src/com/google/common/collect/AbstractMapBasedMultimap.java b/android/guava/src/com/google/common/collect/AbstractMapBasedMultimap.java index b72f13559331..0e7311067c4f 100644 --- a/android/guava/src/com/google/common/collect/AbstractMapBasedMultimap.java +++ b/android/guava/src/com/google/common/collect/AbstractMapBasedMultimap.java @@ -18,7 +18,11 @@ 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.collect.Maps.ViewCachingAbstractMap; @@ -40,7 +44,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 +85,8 @@ * @author Louis Wasserman */ @GwtCompatible -abstract class AbstractMapBasedMultimap extends AbstractMultimap - implements Serializable { +abstract class AbstractMapBasedMultimap + extends AbstractMultimap implements Serializable { /* * Here's an outline of the overall design. * @@ -155,7 +159,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 +175,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 +201,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 +218,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 +247,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 +262,8 @@ public Collection removeAll(@NullableDecl Object key) { return unmodifiableCollectionSubclass(output); } - Collection unmodifiableCollectionSubclass(Collection collection) { + Collection unmodifiableCollectionSubclass( + Collection collection) { return Collections.unmodifiableCollection(collection); } @@ -280,7 +285,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 +297,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 +325,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 +371,7 @@ void removeIfEmpty() { } } + @ParametricNullness K getKey() { return key; } @@ -391,7 +397,7 @@ public int size() { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -451,6 +457,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public V next() { validateIterator(); return delegateIterator.next(); @@ -470,7 +477,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 +490,7 @@ public boolean add(V value) { return changed; } - WrappedCollection getAncestor() { + @Nullable WrappedCollection getAncestor() { return ancestor; } @@ -507,7 +514,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 +537,7 @@ public void clear() { } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { refreshIfEmpty(); boolean changed = delegate.remove(o); if (changed) { @@ -569,7 +576,8 @@ public boolean retainAll(Collection c) { } } - private static Iterator iteratorOrListIterator(Collection collection) { + private static Iterator iteratorOrListIterator( + Collection collection) { return (collection instanceof List) ? ((List) collection).listIterator() : collection.iterator(); @@ -578,7 +586,7 @@ 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) { + WrappedSet(@ParametricNullness K key, Set delegate) { super(key, delegate, null); } @@ -606,7 +614,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 +623,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 +651,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 +660,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(), @@ -662,7 +672,7 @@ public SortedSet tailSet(V fromElement) { @WeakOuter 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 +682,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,18 +727,21 @@ 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)); } } @@ -736,7 +749,7 @@ public NavigableSet tailSet(V fromElement, boolean 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) { + WrappedList(@ParametricNullness K key, List delegate, @Nullable WrappedCollection ancestor) { super(key, delegate, ancestor); } @@ -762,19 +775,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 +800,7 @@ public void add(int index, V element) { } @Override + @ParametricNullness public V remove(int index) { refreshIfEmpty(); V value = getListDelegate().remove(index); @@ -794,13 +810,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); } @@ -844,6 +860,7 @@ public boolean hasPrevious() { } @Override + @ParametricNullness public V previous() { return getDelegateListIterator().previous(); } @@ -859,12 +876,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++; @@ -881,7 +898,7 @@ public void add(V value) { */ private 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); } } @@ -911,7 +928,7 @@ private class KeySet extends Maps.KeySet> { public Iterator iterator() { final Iterator>> entryIterator = map().entrySet().iterator(); return new Iterator() { - @NullableDecl Entry> entry; + @Nullable Entry> entry; @Override public boolean hasNext() { @@ -919,6 +936,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public K next() { entry = entryIterator.next(); return entry.getKey(); @@ -926,7 +944,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 +957,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 +979,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 +1001,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 +1045,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 +1085,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 +1132,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 +1145,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 +1153,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { if (!valueIterator.hasNext()) { Entry> mapEntry = keyIterator.next(); @@ -1136,13 +1161,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 +1202,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 +1255,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 +1296,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 +1322,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 +1336,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,7 +1361,7 @@ 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 @@ -1345,16 +1379,17 @@ 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; } @@ -1363,7 +1398,7 @@ public boolean remove(Object o) { /** Iterator across all keys and value collections. */ class AsMapIterator implements Iterator>> { final Iterator>> delegateIterator = submap.entrySet().iterator(); - @NullableDecl Collection collection; + @Nullable Collection collection; @Override public boolean hasNext() { @@ -1379,7 +1414,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 +1434,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 +1482,7 @@ SortedSet createKeySet() { } } - class NavigableAsMap extends SortedAsMap implements NavigableMap> { + private final class NavigableAsMap extends SortedAsMap implements NavigableMap> { NavigableAsMap(NavigableMap> submap) { super(submap); @@ -1456,72 +1494,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 +1568,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,33 +1597,38 @@ 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)); } } diff --git a/android/guava/src/com/google/common/collect/AbstractMapBasedMultiset.java b/android/guava/src/com/google/common/collect/AbstractMapBasedMultiset.java index 3ff472a9fc25..d75332a39028 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,11 +32,11 @@ 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. @@ -43,19 +44,20 @@ * @author Kevin Bourrillion */ @GwtCompatible(emulated = true) -abstract class AbstractMapBasedMultiset extends AbstractMultiset implements Serializable { +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); } @@ -249,19 +254,22 @@ public final int size() { * its count, and so on */ @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); Serialization.writeMultiset(this, stream); } @GwtIncompatible // java.io.ObjectInputStream + @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. + @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..6b87b90b1641 100644 --- a/android/guava/src/com/google/common/collect/AbstractMapEntry.java +++ b/android/guava/src/com/google/common/collect/AbstractMapEntry.java @@ -19,7 +19,7 @@ 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 org.jspecify.annotations.Nullable; /** * Implementation of the {@code equals}, {@code hashCode}, and {@code toString} methods of {@code @@ -28,21 +28,25 @@ * @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()) diff --git a/android/guava/src/com/google/common/collect/AbstractMultimap.java b/android/guava/src/com/google/common/collect/AbstractMultimap.java index a9952172d104..cca1bf9cea15 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() { @@ -137,14 +138,14 @@ public int hashCode() { } @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() { @@ -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..55848b24b496 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() { @@ -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..7a879a4a885a 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() { @@ -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..8c2d24be124b 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 @@ -41,13 +41,13 @@ */ @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..afb34e96de29 100644 --- a/android/guava/src/com/google/common/collect/AbstractSetMultimap.java +++ b/android/guava/src/com/google/common/collect/AbstractSetMultimap.java @@ -16,14 +16,16 @@ 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.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 +35,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 +51,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 +74,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 +97,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 +111,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 +136,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,7 +147,7 @@ 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); } 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..27ab0dd4213e 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. @@ -33,7 +34,8 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) -abstract class AbstractSortedMultiset extends AbstractMultiset implements SortedMultiset { +abstract class AbstractSortedMultiset extends AbstractMultiset + implements SortedMultiset { @GwtTransient final Comparator comparator; // needed for serialization @@ -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() { diff --git a/android/guava/src/com/google/common/collect/AbstractSortedSetMultimap.java b/android/guava/src/com/google/common/collect/AbstractSortedSetMultimap.java index 6254a6e62980..b137973f87ec 100644 --- a/android/guava/src/com/google/common/collect/AbstractSortedSetMultimap.java +++ b/android/guava/src/com/google/common/collect/AbstractSortedSetMultimap.java @@ -16,14 +16,16 @@ 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.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 +35,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 +55,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 +86,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 +99,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 +115,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); } diff --git a/android/guava/src/com/google/common/collect/AbstractTable.java b/android/guava/src/com/google/common/collect/AbstractTable.java index 7cb34a61bb9a..9f71fdfecd16 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() { @@ -123,25 +129,25 @@ Set> createCellSet() { @WeakOuter 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(); } @@ -191,7 +198,7 @@ public Iterator iterator() { } @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..9ac88ca94923 100644 --- a/android/guava/src/com/google/common/collect/AllEqualOrdering.java +++ b/android/guava/src/com/google/common/collect/AllEqualOrdering.java @@ -19,7 +19,7 @@ import com.google.common.annotations.GwtCompatible; 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. @@ -27,16 +27,17 @@ * @author Emily Soldal */ @GwtCompatible(serializable = true) -final class AllEqualOrdering extends Ordering implements Serializable { +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 +48,7 @@ public ImmutableList immutableSortedCopy(Iterable iterable) { @SuppressWarnings("unchecked") @Override - public Ordering reverse() { + public Ordering reverse() { return (Ordering) this; } diff --git a/android/guava/src/com/google/common/collect/ArrayListMultimap.java b/android/guava/src/com/google/common/collect/ArrayListMultimap.java index 1faf476c6576..6eb18a3ee8d1 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,14 +54,13 @@ * 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 +public final class ArrayListMultimap extends ArrayListMultimapGwtSerializationDependencies { // 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); } @@ -128,7 +135,7 @@ private ArrayListMultimap(Multimap multimap) { */ @Override List createCollection() { - return new ArrayList(expectedValuesPerKey); + return new ArrayList<>(expectedValuesPerKey); } /** @@ -151,12 +158,14 @@ public void trimToSize() { * key, number of values for that key, and the key's values */ @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); Serialization.writeMultimap(this, stream); } @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; @@ -167,5 +176,6 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo } @GwtIncompatible // Not needed in emulated source. + @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 index 9a8cdfbdbd13..6d0eef163409 100644 --- a/android/guava/src/com/google/common/collect/ArrayListMultimapGwtSerializationDependencies.java +++ b/android/guava/src/com/google/common/collect/ArrayListMultimapGwtSerializationDependencies.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.Collection; import java.util.Map; +import org.jspecify.annotations.Nullable; /** * A dummy superclass to support GWT serialization of the element types of an {@link @@ -30,7 +31,8 @@ *

    TODO(cpovirk): Consider applying this subclass approach to our other types. */ @GwtCompatible(emulated = true) -abstract class ArrayListMultimapGwtSerializationDependencies +abstract class ArrayListMultimapGwtSerializationDependencies< + K extends @Nullable Object, V extends @Nullable Object> extends AbstractListMultimap { ArrayListMultimapGwtSerializationDependencies(Map> map) { super(map); diff --git a/android/guava/src/com/google/common/collect/ArrayTable.java b/android/guava/src/com/google/common/collect/ArrayTable.java index 2859085c7c7b..a72f31ef7514 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.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; @@ -35,7 +37,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; /** * Fixed-size {@link Table} implementation backed by a two-dimensional array. @@ -81,14 +83,14 @@ * 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 { +public final class ArrayTable extends AbstractTable + implements Serializable { /** * Creates an {@code ArrayTable} filled with {@code null}. @@ -128,8 +130,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 +143,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 +160,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 +177,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 +203,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() { @@ -224,12 +228,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); } }; @@ -248,12 +254,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 +269,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 +279,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 +317,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 +338,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 +357,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 +382,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 +392,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 +401,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,13 +410,13 @@ 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)) { return true; @@ -420,7 +427,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 +449,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 +475,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 +489,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 +507,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 +535,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(final int index) { return getCell(index); } }; } - private Cell getCell(final int index) { - return new Tables.AbstractCell() { + private Cell getCell(final int index) { + return new Tables.AbstractCell() { final int rowIndex = index / columnList.size(); final int columnIndex = index % columnList.size(); @@ -558,13 +565,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 +589,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 class Column extends ArrayMap { final int columnIndex; Column(int columnIndex) { @@ -602,12 +613,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 +634,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 class ColumnMap extends ArrayMap> { private ColumnMap() { super(columnKeyToIndex); } @@ -643,17 +654,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 +681,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 class Row extends ArrayMap { final int rowIndex; Row(int rowIndex) { @@ -690,12 +705,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 +726,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 class RowMap extends ArrayMap> { private RowMap() { super(rowKeyToIndex); } @@ -731,17 +746,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,15 +771,15 @@ 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); } }; diff --git a/android/guava/src/com/google/common/collect/BiMap.java b/android/guava/src/com/google/common/collect/BiMap.java index 0fd75c0a1e5e..9adbff493fad 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 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..e21cfbca6c60 100644 --- a/android/guava/src/com/google/common/collect/ByFunctionOrdering.java +++ b/android/guava/src/com/google/common/collect/ByFunctionOrdering.java @@ -22,14 +22,15 @@ import com.google.common.base.Function; import com.google.common.base.Objects; import java.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +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 { +final class ByFunctionOrdering + extends Ordering implements Serializable { final Function function; final Ordering ordering; @@ -39,12 +40,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; } diff --git a/android/guava/src/com/google/common/collect/CartesianList.java b/android/guava/src/com/google/common/collect/CartesianList.java index 63c7f1a3efe2..8150370afd04 100644 --- a/android/guava/src/com/google/common/collect/CartesianList.java +++ b/android/guava/src/com/google/common/collect/CartesianList.java @@ -17,12 +17,14 @@ import static com.google.common.base.Preconditions.checkElementIndex; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.math.IntMath; 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 +46,7 @@ static List> create(List> lists) { } axesBuilder.add(copy); } - return new CartesianList(axesBuilder.build()); + return new CartesianList<>(axesBuilder.build()); } CartesianList(ImmutableList> axes) { @@ -67,7 +69,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 +91,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 +113,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 +133,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 +151,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..f12c882093ce --- /dev/null +++ b/android/guava/src/com/google/common/collect/CollectCollectors.java @@ -0,0 +1,464 @@ +/* + * 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 +@SuppressWarnings("Java7ApiChecker") +@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 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/CollectSpliterators.java b/android/guava/src/com/google/common/collect/CollectSpliterators.java new file mode 100644 index 000000000000..207aa2d09c96 --- /dev/null +++ b/android/guava/src/com/google/common/collect/CollectSpliterators.java @@ -0,0 +1,560 @@ +/* + * 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 +@SuppressWarnings("Java7ApiChecker") +@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 + 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 + 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..3ffc20077181 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 @@ -81,21 +82,22 @@ private Collections2() {} */ // 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 +110,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 +119,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 +129,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 +152,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,7 +177,7 @@ public Iterator iterator() { } @Override - public boolean remove(Object element) { + public boolean remove(@Nullable Object element) { return contains(element) && unfiltered.remove(element); } @@ -220,13 +221,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 +252,13 @@ 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 { + static class TransformedCollection + extends AbstractCollection { final Collection fromCollection; final Function function; @@ -326,7 +329,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 +354,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()); @@ -403,7 +405,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 +467,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,7 +482,7 @@ public String toString() { } private static final class OrderedPermutationIterator extends AbstractIterator> { - @NullableDecl List nextPermutation; + @Nullable List nextPermutation; final Comparator comparator; OrderedPermutationIterator(List list, Comparator comparator) { @@ -490,7 +491,7 @@ private static final class OrderedPermutationIterator extends AbstractIterato } @Override - protected List computeNext() { + protected @Nullable List computeNext() { if (nextPermutation == null) { return endOfData(); } @@ -505,6 +506,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 +519,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 +533,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 +565,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 +592,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); @@ -598,7 +613,7 @@ private static class PermutationIterator extends AbstractIterator> { 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 +623,7 @@ private static class PermutationIterator extends AbstractIterator> { } @Override - protected List computeNext() { + protected @Nullable List computeNext() { if (j <= 0) { return endOfData(); } @@ -672,7 +687,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..b2e01092ec16 100644 --- a/android/guava/src/com/google/common/collect/CompactHashMap.java +++ b/android/guava/src/com/google/common/collect/CompactHashMap.java @@ -19,13 +19,20 @@ 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; @@ -43,7 +50,7 @@ import java.util.Map; import java.util.NoSuchElementException; 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; @@ -314,7 +365,6 @@ public V put(@NullableDecl K key, @NullableDecl V value) { if (CompactHashing.getHashPrefix(entry, mask) == hashPrefix && Objects.equal(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.equal(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() { @@ -615,13 +684,13 @@ public int 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() { @@ -682,21 +751,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.equal(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 +776,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 +798,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 +811,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 +829,48 @@ public K getKey() { private void updateLastKnownIndex() { if (lastKnownIndex == -1 || lastKnownIndex >= size() - || !Objects.equal(key, keys[lastKnownIndex])) { + || !Objects.equal(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 +878,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 +888,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.equal(value, value(i))) { return true; } } return false; } - @NullableDecl private transient Collection valuesView; + @LazyInit private transient @Nullable Collection valuesView; @Override public Collection values() { @@ -846,15 +931,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 +952,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 +960,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 +976,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 +984,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 +1005,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 +1019,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..0c55bc8ac9cb 100644 --- a/android/guava/src/com/google/common/collect/CompactHashSet.java +++ b/android/guava/src/com/google/common/collect/CompactHashSet.java @@ -19,8 +19,12 @@ 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; @@ -40,7 +44,7 @@ import java.util.LinkedHashSet; import java.util.NoSuchElementException; 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; @@ -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.equal(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..67ec6b5cffac 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 org.jspecify.annotations.Nullable; /** * Helper classes and static methods for implementing compact hash-based collections. @@ -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); diff --git a/android/guava/src/com/google/common/collect/CompactLinkedHashMap.java b/android/guava/src/com/google/common/collect/CompactLinkedHashMap.java index 9135524677b3..3979ebf40ada 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 { +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); + 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..d9f9618971d6 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 { +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; @@ -149,21 +153,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 +191,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 +207,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 +229,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 +246,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..7e4fa25b63b4 100644 --- a/android/guava/src/com/google/common/collect/ComparatorOrdering.java +++ b/android/guava/src/com/google/common/collect/ComparatorOrdering.java @@ -21,11 +21,12 @@ import com.google.common.annotations.GwtCompatible; 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 { +final class ComparatorOrdering extends Ordering + implements Serializable { final Comparator comparator; ComparatorOrdering(Comparator comparator) { @@ -33,12 +34,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; } diff --git a/android/guava/src/com/google/common/collect/Comparators.java b/android/guava/src/com/google/common/collect/Comparators.java index 555e5b80bfaa..8ff31df70dfb 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,108 @@ 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: + * + *

    {@code
    +   * 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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: + * + *

    {@code
    +   * 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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)); + } + + @SuppressWarnings("Java7ApiChecker") + @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 +224,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 +243,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 +262,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 +281,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..c16afa0afb72 100644 --- a/android/guava/src/com/google/common/collect/ComparisonChain.java +++ b/android/guava/src/com/google/common/collect/ComparisonChain.java @@ -17,11 +17,9 @@ 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: @@ -49,7 +47,7 @@ * 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}. * * @author Mark Davis @@ -67,26 +65,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 +99,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 +129,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 +178,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 +197,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 +231,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..7008d229502b 100644 --- a/android/guava/src/com/google/common/collect/CompoundOrdering.java +++ b/android/guava/src/com/google/common/collect/CompoundOrdering.java @@ -20,22 +20,27 @@ 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 { +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 +51,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; } diff --git a/android/guava/src/com/google/common/collect/ComputationException.java b/android/guava/src/com/google/common/collect/ComputationException.java index dc569da3544b..4c0b261df0df 100644 --- a/android/guava/src/com/google/common/collect/ComputationException.java +++ b/android/guava/src/com/google/common/collect/ComputationException.java @@ -17,7 +17,7 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Wraps an exception that occurred during a computation. @@ -36,7 +36,7 @@ @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); } diff --git a/android/guava/src/com/google/common/collect/ConcurrentHashMultiset.java b/android/guava/src/com/google/common/collect/ConcurrentHashMultiset.java index 8bdfca32d0d4..ea831cd57562 100644 --- a/android/guava/src/com/google/common/collect/ConcurrentHashMultiset.java +++ b/android/guava/src/com/google/common/collect/ConcurrentHashMultiset.java @@ -18,11 +18,15 @@ 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; @@ -41,20 +45,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 { @@ -74,7 +78,7 @@ 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 = + static final FieldSetter> COUNT_MAP_FIELD_SETTER = Serialization.getFieldSetter(ConcurrentHashMultiset.class, "countMap"); } @@ -86,7 +90,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 +121,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 +140,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 +181,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 +209,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) { @@ -261,26 +265,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 +312,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 +352,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 +409,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 +450,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 +458,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 +468,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 +484,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 +507,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 +528,7 @@ protected Entry computeNext() { }; return new ForwardingIterator>() { - @NullableDecl private Entry last; + private @Nullable Entry last; @Override protected Iterator> delegate() { @@ -537,7 +543,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; } @@ -572,29 +578,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..bb5aa11a4a0e 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; @@ -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..241f369d1d06 100644 --- a/android/guava/src/com/google/common/collect/Cut.java +++ b/android/guava/src/com/google/common/collect/Cut.java @@ -17,10 +17,9 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; -import com.google.common.primitives.Booleans; 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 +30,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 +53,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 +79,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 +88,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; @@ -120,7 +121,13 @@ 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 @@ -219,7 +226,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 @@ -297,7 +305,7 @@ private Object readResolve() { } static Cut belowValue(C endpoint) { - return new BelowValue(endpoint); + return new BelowValue<>(endpoint); } private static final class BelowValue extends Cut { @@ -326,24 +334,22 @@ Cut withLowerBoundType(BoundType boundType, DiscreteDomain domain) { case CLOSED: return this; case OPEN: - @NullableDecl C previous = domain.previous(endpoint); + C previous = domain.previous(endpoint); return (previous == null) ? Cut.belowAll() : new AboveValue(previous); - default: - throw new AssertionError(); } + throw new AssertionError(); } @Override Cut withUpperBoundType(BoundType boundType, DiscreteDomain domain) { switch (boundType) { case CLOSED: - @NullableDecl C previous = domain.previous(endpoint); + 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 +368,7 @@ C leastValueAbove(DiscreteDomain domain) { } @Override - C greatestValueBelow(DiscreteDomain domain) { + @Nullable C greatestValueBelow(DiscreteDomain domain) { return domain.previous(endpoint); } @@ -380,7 +386,7 @@ public String toString() { } static Cut aboveValue(C endpoint) { - return new AboveValue(endpoint); + return new AboveValue<>(endpoint); } private static final class AboveValue extends Cut { @@ -409,24 +415,22 @@ Cut withLowerBoundType(BoundType boundType, DiscreteDomain domain) { case OPEN: return this; case CLOSED: - @NullableDecl C next = domain.next(endpoint); + C next = domain.next(endpoint); return (next == null) ? Cut.belowAll() : belowValue(next); - default: - throw new AssertionError(); } + throw new AssertionError(); } @Override Cut withUpperBoundType(BoundType boundType, DiscreteDomain domain) { switch (boundType) { case OPEN: - @NullableDecl C next = domain.next(endpoint); + 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 +444,7 @@ void describeAsUpperBound(StringBuilder sb) { } @Override - C leastValueAbove(DiscreteDomain domain) { + @Nullable C leastValueAbove(DiscreteDomain domain) { return domain.next(endpoint); } diff --git a/android/guava/src/com/google/common/collect/DenseImmutableTable.java b/android/guava/src/com/google/common/collect/DenseImmutableTable.java index 4e28c899ae4a..eef7e8bab983 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 // serialization + @GwtIncompatible // serialization + 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 // serialization + @GwtIncompatible // serialization + 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 // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } @WeakOuter @@ -228,6 +269,15 @@ ImmutableMap getValue(int keyIndex) { boolean isPartialView() { return false; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } @Override @@ -245,7 +295,7 @@ public ImmutableMap> 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 +312,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 // serialization + @GwtIncompatible // serialization + 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..146fac334494 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 @@ -31,10 +32,11 @@ * @author Louis Wasserman */ @GwtCompatible(emulated = true) -abstract class DescendingMultiset extends ForwardingMultiset implements SortedMultiset { +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() { @@ -45,42 +47,45 @@ public Comparator comparator() { 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 +100,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() { @@ -141,12 +146,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..873c64a6f8c7 100644 --- a/android/guava/src/com/google/common/collect/DiscreteDomain.java +++ b/android/guava/src/com/google/common/collect/DiscreteDomain.java @@ -25,6 +25,7 @@ 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 +37,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 +67,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; } @@ -109,6 +114,9 @@ public String toString() { /** * 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 +131,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; } @@ -181,6 +189,9 @@ public String toString() { /** * 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() { @@ -248,11 +259,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 +279,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 +289,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..1ce608eeff15 100644 --- a/android/guava/src/com/google/common/collect/EmptyContiguousSet.java +++ b/android/guava/src/com/google/common/collect/EmptyContiguousSet.java @@ -13,12 +13,17 @@ */ 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. @@ -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(); @@ -141,6 +146,7 @@ public int hashCode() { } @GwtIncompatible // serialization + @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 // serialization + @J2ktIncompatible @Override Object writeReplace() { - return new SerializedForm(domain); + return new SerializedForm<>(domain); + } + + @GwtIncompatible // serialization + @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..f01b66c2548f 100644 --- a/android/guava/src/com/google/common/collect/EmptyImmutableListMultimap.java +++ b/android/guava/src/com/google/common/collect/EmptyImmutableListMultimap.java @@ -17,6 +17,7 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import java.util.Collection; /** * Implementation of {@link ImmutableListMultimap} with no entries. @@ -31,6 +32,17 @@ private EmptyImmutableListMultimap() { 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 } diff --git a/android/guava/src/com/google/common/collect/EmptyImmutableSetMultimap.java b/android/guava/src/com/google/common/collect/EmptyImmutableSetMultimap.java index ec2ce2e192b8..1633fa7a406a 100644 --- a/android/guava/src/com/google/common/collect/EmptyImmutableSetMultimap.java +++ b/android/guava/src/com/google/common/collect/EmptyImmutableSetMultimap.java @@ -17,6 +17,7 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import java.util.Collection; /** * Implementation of {@link ImmutableListMultimap} with no entries. @@ -31,6 +32,17 @@ private EmptyImmutableSetMultimap() { 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 } diff --git a/android/guava/src/com/google/common/collect/EnumBiMap.java b/android/guava/src/com/google/common/collect/EnumBiMap.java index f72b8b96f706..fcb99ac46d55 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) +@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,9 +151,10 @@ 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); } diff --git a/android/guava/src/com/google/common/collect/EnumHashBiMap.java b/android/guava/src/com/google/common/collect/EnumHashBiMap.java index 0a7e52e12756..54f7dc12b2b8 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; +@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,9 +125,13 @@ 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); } diff --git a/android/guava/src/com/google/common/collect/EnumMultiset.java b/android/guava/src/com/google/common/collect/EnumMultiset.java index af0deef21bce..8cfa4d045d76 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,25 @@ 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) +@J2ktIncompatible 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 +95,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 +108,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 +126,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 +159,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; @@ -294,7 +298,7 @@ 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]; diff --git a/android/guava/src/com/google/common/collect/EvictingQueue.java b/android/guava/src/com/google/common/collect/EvictingQueue.java index 37a65f3e0deb..3edcd03632ce 100644 --- a/android/guava/src/com/google/common/collect/EvictingQueue.java +++ b/android/guava/src/com/google/common/collect/EvictingQueue.java @@ -19,8 +19,8 @@ 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.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.Serializable; @@ -43,7 +43,6 @@ * @author Kurt Alfred Kluever * @since 15.0 */ -@Beta @GwtCompatible public final class EvictingQueue extends ForwardingQueue implements Serializable { @@ -53,7 +52,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 +63,7 @@ private EvictingQueue(int maxSize) { * queue. */ public static EvictingQueue create(int maxSize) { - return new EvictingQueue(maxSize); + return new EvictingQueue<>(maxSize); } /** @@ -126,17 +125,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; } diff --git a/android/guava/src/com/google/common/collect/ExplicitOrdering.java b/android/guava/src/com/google/common/collect/ExplicitOrdering.java index 710c1aea0722..314c6dcb8da5 100644 --- a/android/guava/src/com/google/common/collect/ExplicitOrdering.java +++ b/android/guava/src/com/google/common/collect/ExplicitOrdering.java @@ -19,7 +19,7 @@ import com.google.common.annotations.GwtCompatible; 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) @@ -48,7 +48,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); diff --git a/android/guava/src/com/google/common/collect/FilteredEntryMultimap.java b/android/guava/src/com/google/common/collect/FilteredEntryMultimap.java index 5860d885c625..c71ad1476dbb 100644 --- a/android/guava/src/com/google/common/collect/FilteredEntryMultimap.java +++ b/android/guava/src/com/google/common/collect/FilteredEntryMultimap.java @@ -20,6 +20,9 @@ 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.unmodifiableList; +import static java.util.Collections.unmodifiableSet; import com.google.common.annotations.GwtCompatible; import com.google.common.base.MoreObjects; @@ -33,7 +36,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; /** * Implementation of {@link Multimaps#filterEntries(Multimap, Predicate)}. @@ -42,7 +45,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 +70,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,12 +97,12 @@ 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()); } @@ -115,7 +119,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 +155,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 { @@ -166,7 +171,7 @@ boolean removeEntriesIf(Predicate>> predicate) { @WeakOuter class AsMap extends ViewCachingAbstractMap> { @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return get(key) != null; } @@ -176,7 +181,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,7 +193,7 @@ 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; @@ -207,9 +212,9 @@ public Collection remove(@NullableDecl Object key) { if (result.isEmpty()) { return null; } else if (unfiltered instanceof SetMultimap) { - return Collections.unmodifiableSet(Sets.newLinkedHashSet(result)); + return unmodifiableSet(Sets.newLinkedHashSet(result)); } else { - return Collections.unmodifiableList(result); + return unmodifiableList(result); } } @@ -232,7 +237,7 @@ public boolean retainAll(Collection c) { } @Override - public boolean remove(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { return AsMap.this.remove(o) != null; } } @@ -255,14 +260,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(); @@ -297,7 +302,7 @@ class ValuesImpl extends Maps.Values> { } @Override - public boolean remove(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { if (o instanceof Collection) { Collection c = (Collection) o; Iterator>> entryIterator = @@ -346,7 +351,7 @@ class Keys extends Multimaps.Keys { } @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 +395,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..1ca1aa0371e7 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,15 @@ 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(); } Collection unmodifiableEmptyCollection() { if (unfiltered instanceof SetMultimap) { - return ImmutableSet.of(); + return emptySet(); } else { - return ImmutableList.of(); + return emptyList(); } } @@ -98,7 +100,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 +110,16 @@ public Collection get(K key) { } } - static class AddRejectingSet extends ForwardingSet { - final K key; + static 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 +131,26 @@ public boolean addAll(Collection collection) { @Override protected Set delegate() { - return Collections.emptySet(); + return emptySet(); } } - static class AddRejectingList extends ForwardingList { - final K key; + static class AddRejectingList + 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); } @@ -167,7 +171,7 @@ public boolean addAll(int index, Collection elements) { @Override protected List delegate() { - return Collections.emptyList(); + return emptyList(); } } @@ -190,7 +194,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..58c473c9846b 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); } @@ -72,7 +72,7 @@ public int hashCode() { } @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..b39dd9a8802f 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 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,7 +60,7 @@ 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(); ) { @@ -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..580d355587f8 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,16 @@ 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 org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * An expanded {@code Iterable} API, providing functionality similar to Java 8's powerful implements Iterable { +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 +125,7 @@ protected FluentIterable() { } FluentIterable(Iterable iterable) { - checkNotNull(iterable); - this.iterableDelegate = Optional.fromNullable(this != iterable ? iterable : null); + this.iterableDelegate = Optional.of(iterable); } private Iterable getDelegate() { @@ -138,7 +139,7 @@ private Iterable getDelegate() { *

    {@code Stream} equivalent: {@code iterable.stream()} if {@code iterable} is a {@link * Collection}; {@code StreamSupport.stream(iterable.spliterator(), false)} otherwise. */ - public static FluentIterable from(final Iterable iterable) { + public static FluentIterable from(final Iterable iterable) { return (iterable instanceof FluentIterable) ? (FluentIterable) iterable : new FluentIterable(iterable) { @@ -159,8 +160,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 +173,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 +192,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 +210,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 +229,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 +252,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,20 +272,19 @@ public static FluentIterable concat(Iterable... inputs) { * * @since 20.0 */ - @Beta - public static FluentIterable concat( + public static FluentIterable concat( final 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( + private static FluentIterable concatNoDefensiveCopy( final Iterable... inputs) { for (Iterable input : inputs) { checkNotNull(input); @@ -310,9 +311,8 @@ public Iterator get(int i) { * * @since 20.0 */ - @Beta - public static FluentIterable of() { - return FluentIterable.from(ImmutableList.of()); + public static FluentIterable of() { + return FluentIterable.from(Collections.emptyList()); } /** @@ -323,8 +323,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)); } @@ -355,7 +355,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 +391,6 @@ public final FluentIterable cycle() { * * @since 18.0 */ - @Beta public final FluentIterable append(Iterable other) { return FluentIterable.concat(getDelegate(), other); } @@ -404,7 +403,6 @@ public final FluentIterable append(Iterable other) { * * @since 18.0 */ - @Beta public final FluentIterable append(E... elements) { return FluentIterable.concat(getDelegate(), Arrays.asList(elements)); } @@ -465,8 +463,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 +478,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 +496,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 +511,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 +528,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 +569,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 @@ -616,8 +618,9 @@ public final boolean isEmpty() { * @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()); } /** @@ -633,8 +636,9 @@ public final ImmutableList toList() { * @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()); } /** @@ -647,8 +651,9 @@ public final ImmutableList toSortedList(Comparator comparator) { * @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()); } /** @@ -665,8 +670,9 @@ public final ImmutableSet toSet() { * @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()); } /** @@ -678,8 +684,9 @@ public final ImmutableSortedSet toSortedSet(Comparator comparator) * @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()); } /** @@ -699,8 +706,9 @@ public final ImmutableMultiset toMultiset() { * 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); } /** @@ -721,8 +729,9 @@ public final ImmutableMap toMap(Function valueFunction) * 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); } /** @@ -757,8 +766,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 +784,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 +823,6 @@ public final > C copyInto(C collection) { * * @since 18.0 */ - @Beta public final String join(Joiner joiner) { return joiner.join(this); } @@ -830,13 +839,14 @@ 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> { + 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..45cb5a86c0f8 100644 --- a/android/guava/src/com/google/common/collect/ForwardingCollection.java +++ b/android/guava/src/com/google/common/collect/ForwardingCollection.java @@ -21,7 +21,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Collection; import java.util.Iterator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +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,7 +167,7 @@ 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)) { @@ -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..ef6893c03732 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,19 +48,19 @@ 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); } 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/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..b1d8bfc866ef 100644 --- a/android/guava/src/com/google/common/collect/ForwardingMap.java +++ b/android/guava/src/com/google/common/collect/ForwardingMap.java @@ -16,7 +16,6 @@ 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; @@ -24,7 +23,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; /** * A map which forwards all its method calls to another map. Subclasses should override one or more @@ -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,8 +157,7 @@ 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(); @@ -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..001af42de011 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 org.jspecify.annotations.Nullable; /** * A map entry which forwards all its method calls to another map entry. Subclasses should override @@ -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,7 +93,7 @@ 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()) @@ -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..98edd44b9b41 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,7 @@ public Collection values() { } @Override - public boolean equals(@NullableDecl Object object) { + 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..5c59b5ab4302 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.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,8 +135,7 @@ 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)) { 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..5f2b4078f5c8 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,8 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingSortedMap extends ForwardingMap - implements SortedMap { +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 +61,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 +99,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 +106,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 +125,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 +144,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..18d3c61d1554 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 { +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..00bc8412c626 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,8 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingSortedSet extends ForwardingSet implements SortedSet { +public abstract class ForwardingSortedSet extends ForwardingSet + implements SortedSet { /** Constructor for use by subclasses. */ protected ForwardingSortedSet() {} @@ -61,44 +63,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 +102,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 +122,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 +148,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..69bd0695563f 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 org.jspecify.annotations.Nullable; /** * A generalized interval on any ordering, for internal use. Supports {@code null}. Unlike {@link @@ -35,15 +37,16 @@ * @author Louis Wasserman */ @GwtCompatible(serializable = true) -final class GeneralRange implements Serializable { +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) @@ -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..787c411d5019 100644 --- a/android/guava/src/com/google/common/collect/HashBasedTable.java +++ b/android/guava/src/com/google/common/collect/HashBasedTable.java @@ -20,11 +20,9 @@ import com.google.common.annotations.GwtCompatible; 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,7 +41,7 @@ * 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 @@ -103,43 +101,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; } diff --git a/android/guava/src/com/google/common/collect/HashBiMap.java b/android/guava/src/com/google/common/collect/HashBiMap.java index 18951fe08be9..21b7166c7989 100644 --- a/android/guava/src/com/google/common/collect/HashBiMap.java +++ b/android/guava/src/com/google/common/collect/HashBiMap.java @@ -15,9 +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.annotations.J2ktIncompatible; import com.google.common.base.Objects; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.concurrent.LazyInit; @@ -34,7 +37,7 @@ import java.util.Map; import java.util.NoSuchElementException; 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,12 +228,16 @@ 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)) { 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,31 +262,28 @@ 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) { @@ -306,13 +322,12 @@ 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) { @@ -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,11 +839,11 @@ 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]); } @@ -831,11 +852,11 @@ public boolean contains(@NullableDecl Object o) { @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])) { @@ -855,14 +876,17 @@ 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; } @@ -873,24 +897,43 @@ void updateIndex() { } @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]; + /* + * 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.equal(oldValue, value)) { return value; } @@ -899,7 +942,7 @@ public V setValue(V value) { } } - @LazyInit @RetainedWith @NullableDecl private transient BiMap inverse; + @LazyInit @RetainedWith private transient @Nullable BiMap inverse; @Override public BiMap inverse() { @@ -907,7 +950,8 @@ public BiMap inverse() { return (result == null) ? inverse = new Inverse(this) : result; } - static class Inverse extends AbstractMap implements BiMap, Serializable { + static 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,13 +1031,14 @@ private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOE } } - static class InverseEntrySet extends View> { + static 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(); @@ -1009,7 +1050,7 @@ public boolean contains(@NullableDecl Object o) { } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { if (o instanceof Entry) { Entry e = (Entry) o; Object v = e.getKey(); @@ -1035,14 +1076,16 @@ 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; } @@ -1053,23 +1096,28 @@ private void updateIndex() { } @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]; + K oldKey = uncheckedCastNullableTToT(biMap.keys[index]); // see EntryForKey.setValue() if (Objects.equal(oldKey, key)) { return key; } @@ -1082,12 +1130,14 @@ 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 + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); Serialization.writeMap(this, stream); } @GwtIncompatible // java.io.ObjectInputStream + @J2ktIncompatible private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); int size = Serialization.readCount(stream); diff --git a/android/guava/src/com/google/common/collect/HashMultimap.java b/android/guava/src/com/google/common/collect/HashMultimap.java index 92972608ecde..785cebe8baf0 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 { +public final class HashMultimap + extends HashMultimapGwtSerializationDependencies { 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); } @@ -122,12 +134,14 @@ Set createCollection() { * key, number of values for that key, and the key's values */ @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); Serialization.writeMultimap(this, stream); } @GwtIncompatible // java.io.ObjectInputStream + @J2ktIncompatible private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; @@ -138,5 +152,6 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo } @GwtIncompatible // Not needed in emulated source + @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 index 0922c3839080..66ec054ec575 100644 --- a/android/guava/src/com/google/common/collect/HashMultimapGwtSerializationDependencies.java +++ b/android/guava/src/com/google/common/collect/HashMultimapGwtSerializationDependencies.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.Collection; import java.util.Map; +import org.jspecify.annotations.Nullable; /** * A dummy superclass to support GWT serialization of the element types of a {@link HashMultimap}. @@ -30,7 +31,9 @@ *

    TODO(cpovirk): Consider applying this subclass approach to our other types. */ @GwtCompatible(emulated = true) -abstract class HashMultimapGwtSerializationDependencies extends AbstractSetMultimap { +abstract class HashMultimapGwtSerializationDependencies< + K extends @Nullable Object, V extends @Nullable Object> + 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..d4915fdd0d1d 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. @@ -27,10 +29,10 @@ * @since 2.0 */ @GwtCompatible(serializable = true, emulated = true) -public class HashMultiset extends AbstractMapBasedMultiset { +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,11 @@ 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. + @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..a5e2448cdd32 --- /dev/null +++ b/android/guava/src/com/google/common/collect/IgnoreJRERequirement.java @@ -0,0 +1,29 @@ +/* + * 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.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}) +@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..2edaab740a6b 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 @@ -35,7 +37,7 @@ 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); @@ -58,6 +60,7 @@ boolean isPartialView() { /** Serialized form that leads to the same performance as the original list. */ @GwtIncompatible // serialization + @J2ktIncompatible static class SerializedForm implements Serializable { final ImmutableCollection collection; @@ -73,11 +76,13 @@ Object readResolve() { } @GwtIncompatible // serialization + @J2ktIncompatible private void readObject(ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Use SerializedForm"); } @GwtIncompatible // serialization + @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..80ec46fd7a12 100644 --- a/android/guava/src/com/google/common/collect/ImmutableBiMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableBiMap.java @@ -19,13 +19,21 @@ 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.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 @@ -37,6 +45,27 @@ @GwtCompatible(serializable = true, emulated = true) 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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); @@ -144,7 +339,7 @@ public static Builder builderWithExpectedSize(int expectedSize) { * .put("one", 1) * .put("two", 2) * .put("three", 3) - * .build(); + * .buildOrThrow(); * } * *

    For small immutable bimaps, the {@code ImmutableBiMap.of()} methods are even more @@ -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,6 +594,7 @@ 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. */ + @J2ktIncompatible // serialization private static class SerializedForm extends ImmutableMap.SerializedForm { SerializedForm(ImmutableBiMap bimap) { super(bimap); @@ -378,7 +609,55 @@ Builder makeBuilder(int size) { } @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}. + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @Deprecated + @DoNotCall("Use toImmutableBiMap") + @SuppressWarnings("Java7ApiChecker") + @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 + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @Deprecated + @DoNotCall("Use toImmutableBiMap") + @SuppressWarnings("Java7ApiChecker") + @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(); + } + + 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..f93a47969eff 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,7 +37,9 @@ */ @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 = @@ -61,7 +64,7 @@ public static ImmutableClassToInstanceMap of() { */ public static ImmutableClassToInstanceMap of(Class type, T value) { ImmutableMap, B> map = ImmutableMap., B>of(type, value); - return new ImmutableClassToInstanceMap(map); + return new ImmutableClassToInstanceMap<>(map); } /** @@ -69,7 +72,7 @@ public static ImmutableClassToInstanceMap of(Class type, * Builder} constructor. */ public static Builder builder() { - return new Builder(); + return new Builder<>(); } /** @@ -90,6 +93,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 +125,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 +136,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 +159,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 +181,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 +195,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..0151e29fba7f 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 @@ -155,7 +161,7 @@ *

    See also

    * *

    See the Guava User Guide article on immutable collections. + * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections. * * @since 2.0 */ @@ -165,6 +171,16 @@ // 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. + */ + @SuppressWarnings("Java7ApiChecker") + // @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 +188,38 @@ public abstract class ImmutableCollection extends AbstractCollection imple @Override public abstract UnmodifiableIterator iterator(); + @Override + @SuppressWarnings("Java7ApiChecker") + @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 +237,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 +258,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 +284,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 +354,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 +370,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; } + @J2ktIncompatible // serialization + @GwtIncompatible // serialization 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 +400,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 +495,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 +511,12 @@ abstract static class ArrayBasedBuilder extends ImmutableCollection.Builder contents.length || forceCopy) { + this.contents = Arrays.copyOf(this.contents, newCapacity); forceCopy = false; } } @@ -479,7 +525,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 +537,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 +557,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 +568,6 @@ public Builder addAll(Iterable elements) { return this; } } + + private static final long serialVersionUID = 0xdecaf; } diff --git a/android/guava/src/com/google/common/collect/ImmutableEntry.java b/android/guava/src/com/google/common/collect/ImmutableEntry.java index cc869b31aa21..2dbcc75f44a9 100644 --- a/android/guava/src/com/google/common/collect/ImmutableEntry.java +++ b/android/guava/src/com/google/common/collect/ImmutableEntry.java @@ -18,33 +18,39 @@ import com.google.common.annotations.GwtCompatible; import java.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; -/** @see com.google.common.collect.Maps#immutableEntry(Object, Object) */ +/** + * An immutable {@code Map.Entry}, used both by {@link + * com.google.common.collect.Maps#immutableEntry(Object, Object)} and by other parts of {@code + * common.collect} as a superclass. + */ @GwtCompatible(serializable = true) -class ImmutableEntry extends AbstractMapEntry implements Serializable { - @NullableDecl final K key; - @NullableDecl final V value; +class ImmutableEntry + extends AbstractMapEntry implements Serializable { + @ParametricNullness final K key; + @ParametricNullness final V value; - ImmutableEntry(@NullableDecl K key, @NullableDecl V value) { + ImmutableEntry(@ParametricNullness K key, @ParametricNullness V value) { this.key = key; this.value = value; } @Override - @NullableDecl + @ParametricNullness public final K getKey() { return key; } @Override - @NullableDecl + @ParametricNullness public final V getValue() { return value; } @Override - public final V setValue(V value) { + @ParametricNullness + public final V setValue(@ParametricNullness V value) { throw new UnsupportedOperationException(); } diff --git a/android/guava/src/com/google/common/collect/ImmutableEnumMap.java b/android/guava/src/com/google/common/collect/ImmutableEnumMap.java index beab47a9a0f7..f597e994e3ff 100644 --- a/android/guava/src/com/google/common/collect/ImmutableEnumMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableEnumMap.java @@ -17,12 +17,16 @@ 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.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}. @@ -37,7 +41,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 +66,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,13 +98,20 @@ 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. */ + @J2ktIncompatible // serialization private static class EnumSerializedForm, V> implements Serializable { final EnumMap delegate; diff --git a/android/guava/src/com/google/common/collect/ImmutableEnumSet.java b/android/guava/src/com/google/common/collect/ImmutableEnumSet.java index 4e189ffd0ade..e888f4853fa7 100644 --- a/android/guava/src/com/google/common/collect/ImmutableEnumSet.java +++ b/android/guava/src/com/google/common/collect/ImmutableEnumSet.java @@ -16,11 +16,17 @@ package com.google.common.collect; +import static com.google.common.collect.Iterables.getOnlyElement; + import com.google.common.annotations.GwtCompatible; +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}. @@ -30,15 +36,14 @@ @GwtCompatible(serializable = true, emulated = true) @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 +77,7 @@ public int size() { } @Override - public boolean contains(Object object) { + public boolean contains(@Nullable Object object) { return delegate.contains(object); } @@ -90,7 +95,7 @@ public boolean isEmpty() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -120,13 +125,20 @@ public String 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. */ + @J2ktIncompatible // serialization private static class EnumSerializedForm> implements Serializable { final EnumSet delegate; diff --git a/android/guava/src/com/google/common/collect/ImmutableList.java b/android/guava/src/com/google/common/collect/ImmutableList.java index da2efb1312bf..510343a5041c 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,14 +41,15 @@ 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 @@ -57,6 +60,19 @@ @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +91,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 +245,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. @@ -285,7 +301,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 +324,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 +355,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,6 +388,8 @@ 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); @@ -390,17 +408,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 +428,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) { @@ -447,7 +471,7 @@ public int size() { } @Override - Object[] internalArray() { + @Nullable Object @Nullable [] internalArray() { return ImmutableList.this.internalArray(); } @@ -477,6 +501,15 @@ public ImmutableList subList(int fromIndex, int toIndex) { boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } /** @@ -540,6 +573,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 +581,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++) { @@ -588,18 +622,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 +659,19 @@ public int size() { boolean isPartialView() { return forwardList.isPartialView(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { return Lists.equalsImpl(this, obj); } @@ -649,6 +692,7 @@ public int hashCode() { * Serializes ImmutableLists as their logical contents. This ensures that * implementation types do not leak into the serialized representation. */ + @J2ktIncompatible // serialization static class SerializedForm implements Serializable { final Object[] elements; @@ -663,11 +707,14 @@ Object readResolve() { private static final long serialVersionUID = 0; } + @J2ktIncompatible // serialization private void readObject(ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Use SerializedForm"); } @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization Object writeReplace() { return new SerializedForm(toArray()); } @@ -677,7 +724,7 @@ Object writeReplace() { * Builder} constructor. */ public static Builder builder() { - return new Builder(); + return new Builder<>(); } /** @@ -692,10 +739,9 @@ 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); } /** @@ -801,5 +847,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); + } } + + 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..f9d46e159eaa 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,14 +34,17 @@ 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 @@ -46,6 +52,82 @@ @GwtCompatible(serializable = true, emulated = true) 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: + * + *

    {@code
    +   * 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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: + * + *

    {@code
    +   * 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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,6 +197,19 @@ 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: @@ -141,6 +236,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 +278,6 @@ public Builder put(Entry entry) { * @since 19.0 */ @CanIgnoreReturnValue - @Beta @Override public Builder putAll(Iterable> entries) { super.putAll(entries); @@ -194,6 +305,13 @@ public Builder putAll(Multimap multimap) { return this; } + @CanIgnoreReturnValue + @Override + Builder combine(ImmutableMultimap.Builder other) { + super.combine(other); + return this; + } + /** * {@inheritDoc} * @@ -218,13 +336,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 +380,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 +388,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 +409,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 +447,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; } - @LazyInit @RetainedWith private transient ImmutableListMultimap inverse; + @LazyInit @RetainedWith private transient @Nullable ImmutableListMultimap inverse; /** * {@inheritDoc} @@ -357,7 +490,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(); } @@ -380,12 +513,14 @@ public final ImmutableList replaceValues(K key, Iterable values) * values for that key, and the key's values */ @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); Serialization.writeMultimap(this, stream); } @GwtIncompatible // java.io.ObjectInputStream + @J2ktIncompatible private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); int keyCount = stream.readInt(); @@ -396,7 +531,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 +539,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 +547,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); } @@ -422,5 +557,6 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo } @GwtIncompatible // Not needed in emulated source + @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..a06ec906e765 100644 --- a/android/guava/src/com/google/common/collect/ImmutableMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableMap.java @@ -20,33 +20,45 @@ 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 @@ -57,6 +69,50 @@ @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +186,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 +387,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); } @@ -193,7 +415,7 @@ static IllegalArgumentException conflictException( * .put("one", 1) * .put("two", 2) * .put("three", 3) - * .build(); + * .buildOrThrow(); * } * *

    For small immutable maps, the {@code ImmutableMap.of()} methods are even more @@ -207,18 +429,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 +455,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 +474,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 +489,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 +501,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 +513,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 +542,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 +552,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 +562,157 @@ 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(Maps.valueFunction())); + 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 +747,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 = @@ -444,6 +780,15 @@ ImmutableMap map() { public UnmodifiableIterator> iterator() { return entryIterator(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } return new EntrySetImpl(); } @@ -452,6 +797,15 @@ public UnmodifiableIterator> iterator() { ImmutableCollection createValues() { return new ImmutableMapValues<>(this); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } ImmutableMap() {} @@ -466,7 +820,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 +833,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 +869,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 +891,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 +944,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 @@ -593,7 +978,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 +998,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 +1031,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); } @@ -698,10 +1083,19 @@ public ImmutableSet getValue() { } }; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + 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 +1120,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 +1137,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 +1169,7 @@ final Object readResolve() { builder.put(keyIter.next(), valueIter.next()); } - return builder.build(); + return builder.buildOrThrow(); } @SuppressWarnings("unchecked") @@ -786,7 +1182,7 @@ final Object legacyReadResolve() { for (int i = 0; i < keys.length; i++) { builder.put(keys[i], values[i]); } - return builder.build(); + return builder.buildOrThrow(); } /** @@ -804,7 +1200,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"); + } + + 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..93686b9719d4 100644 --- a/android/guava/src/com/google/common/collect/ImmutableMapEntrySet.java +++ b/android/guava/src/com/google/common/collect/ImmutableMapEntrySet.java @@ -18,9 +18,12 @@ 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}. @@ -50,7 +53,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 +66,15 @@ public UnmodifiableIterator> iterator() { ImmutableList> createAsList() { return entries; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } ImmutableMapEntrySet() {} @@ -75,7 +87,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()); @@ -101,12 +113,20 @@ public int hashCode() { } @GwtIncompatible // serialization + @J2ktIncompatible @Override Object writeReplace() { return new EntrySetSerializedForm<>(map()); } @GwtIncompatible // serialization + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use EntrySetSerializedForm"); + } + + @GwtIncompatible // serialization + @J2ktIncompatible private static class EntrySetSerializedForm implements Serializable { final ImmutableMap map; diff --git a/android/guava/src/com/google/common/collect/ImmutableMapKeySet.java b/android/guava/src/com/google/common/collect/ImmutableMapKeySet.java index 77babc276e5d..c179ec336c9c 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}. @@ -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,13 +61,15 @@ boolean isPartialView() { return true; } - @GwtIncompatible // serialization @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization Object writeReplace() { return new KeySetSerializedForm(map); } @GwtIncompatible // serialization + @J2ktIncompatible private static class KeySetSerializedForm implements Serializable { final ImmutableMap map; diff --git a/android/guava/src/com/google/common/collect/ImmutableMapValues.java b/android/guava/src/com/google/common/collect/ImmutableMapValues.java index 4a0e3960d581..b7cc10ca02a7 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}. @@ -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); } @@ -86,6 +87,15 @@ boolean isPartialView() { public int size() { return entryList.size(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } }; } @@ -96,6 +106,7 @@ Object writeReplace() { } @GwtIncompatible // serialization + @J2ktIncompatible private static class SerializedForm implements Serializable { final ImmutableMap map; diff --git a/android/guava/src/com/google/common/collect/ImmutableMultimap.java b/android/guava/src/com/google/common/collect/ImmutableMultimap.java index 61472f65dcb7..bdd2c453054e 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,7 +65,7 @@ * 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 @@ -121,6 +128,19 @@ 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: @@ -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); + } + + /** + * 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; } - Collection newMutableValueCollection() { - return new ArrayList<>(); + /** + * 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); @@ -337,10 +412,11 @@ public static ImmutableMultimap copyOf( // holder class makes sure they are not initialized unless an instance is // deserialized. @GwtIncompatible // java serialization is not supported + @J2ktIncompatible static class FieldSettersHolder { - static final Serialization.FieldSetter MAP_FIELD_SETTER = + 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"); } @@ -360,7 +436,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 +453,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 +469,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 +499,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 +513,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 +527,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 +541,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 +559,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); } @@ -549,7 +638,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,6 +646,15 @@ public boolean contains(Object object) { return false; } + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } + private static final long serialVersionUID = 0; } @@ -565,8 +663,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 +678,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()); } }; } @@ -604,12 +706,12 @@ ImmutableMultiset createKeys() { @WeakOuter 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 +738,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; @@ -673,7 +783,7 @@ ImmutableCollection createValues() { UnmodifiableIterator valueIterator() { return new UnmodifiableIterator() { Iterator> valueCollectionItr = map.values().iterator(); - Iterator valueItr = Iterators.emptyIterator(); + Iterator valueItr = emptyIterator(); @Override public boolean hasNext() { @@ -698,7 +808,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 +819,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 +836,19 @@ boolean isPartialView() { return true; } + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } + + @J2ktIncompatible // serialization private static final long serialVersionUID = 0; } + @J2ktIncompatible // serialization 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..c261fe3e5220 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,7 +47,7 @@ * 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 @@ -50,6 +57,40 @@ @SuppressWarnings("serial") // we're overriding default serialization public abstract class ImmutableMultiset extends ImmutableMultisetGwtSerializationDependencies 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +104,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 +117,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 +128,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 +139,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 +150,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 +161,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(); } @@ -191,7 +226,7 @@ public UnmodifiableIterator iterator() { final Iterator> entryIterator = entrySet().iterator(); return new UnmodifiableIterator() { int remaining; - @NullableDecl E element; + @Nullable E element; @Override public boolean hasNext() { @@ -206,12 +241,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 +259,7 @@ public ImmutableList asList() { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { return count(object) > 0; } @@ -248,7 +287,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 +321,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 +330,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 +344,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() { @@ -341,7 +382,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,15 +400,23 @@ public int hashCode() { } @GwtIncompatible + @J2ktIncompatible @Override 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"); + } + + @J2ktIncompatible private static final long serialVersionUID = 0; } @GwtIncompatible + @J2ktIncompatible static class EntrySetSerializedForm implements Serializable { final ImmutableMultiset multiset; @@ -381,15 +430,22 @@ Object readResolve() { } @GwtIncompatible + @J2ktIncompatible @Override 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<>(); } /** @@ -412,13 +468,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 +545,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 +571,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 +601,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 +642,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 +658,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 +673,6 @@ public ImmutableMultiset build() { return new RegularImmutableMultiset(contents); } } + + private static final long serialVersionUID = 0xdecaf; } diff --git a/android/guava/src/com/google/common/collect/ImmutableRangeMap.java b/android/guava/src/com/google/common/collect/ImmutableRangeMap.java index 2183ef85e675..179f1772a63e 100644 --- a/android/guava/src/com/google/common/collect/ImmutableRangeMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableRangeMap.java @@ -17,21 +17,26 @@ 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 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.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,13 +45,27 @@ * @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()); + /** + * 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +89,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()); @@ -106,7 +125,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 +151,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 +179,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 +196,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 +208,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 +257,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(); } @@ -300,14 +317,14 @@ 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); @@ -337,6 +354,14 @@ 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; return new ImmutableRangeMap(subRanges, values.subList(lowerIndex, upperIndex)) { @@ -348,6 +373,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 +390,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()); @@ -405,5 +438,10 @@ Object writeReplace() { return new SerializedForm<>(asMapOfRanges()); } + @J2ktIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + 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..1aadf1eebf33 100644 --- a/android/guava/src/com/google/common/collect/ImmutableRangeSet.java +++ b/android/guava/src/com/google/common/collect/ImmutableRangeSet.java @@ -17,25 +17,32 @@ 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.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 java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; 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,7 +51,7 @@ * @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 { @@ -55,6 +62,20 @@ public final class ImmutableRangeSet extends AbstractRange private static final ImmutableRangeSet> 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +97,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)); } } @@ -101,7 +122,7 @@ public static ImmutableRangeSet copyOf(RangeSet ran return immutableRangeSet; } } - return new ImmutableRangeSet(ImmutableList.copyOf(rangeSet.asRanges())); + return new ImmutableRangeSet<>(ImmutableList.copyOf(rangeSet.asRanges())); } /** @@ -144,7 +165,7 @@ public boolean intersects(Range otherRange) { int ceilingIndex = SortedLists.binarySearch( ranges, - Range.lowerBoundFn(), + Range::lowerBound, otherRange.lowerBound, Ordering.natural(), ANY_PRESENT, @@ -164,7 +185,7 @@ public boolean encloses(Range otherRange) { int index = SortedLists.binarySearch( ranges, - Range.lowerBoundFn(), + Range::lowerBound, otherRange.lowerBound, Ordering.natural(), ANY_PRESENT, @@ -173,11 +194,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, @@ -296,7 +317,7 @@ public ImmutableSet> asDescendingSetOfRanges() { return new RegularImmutableSortedSet<>(ranges.reverse(), Range.rangeLexOrdering().reverse()); } - @LazyInit private transient ImmutableRangeSet complement; + @LazyInit private transient @Nullable ImmutableRangeSet complement; private final class ComplementRanges extends ImmutableList> { // True if the "positive" range set is empty or bounded below. @@ -351,6 +372,14 @@ 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 @@ -364,7 +393,7 @@ public ImmutableRangeSet complement() { return complement = of(); } else { ImmutableList> complementRanges = new ComplementRanges(); - result = complement = new ImmutableRangeSet(complementRanges, this); + result = complement = new ImmutableRangeSet<>(complementRanges, this); } return result; } @@ -426,7 +455,7 @@ private ImmutableList> intersectRanges(final Range range) { fromIndex = SortedLists.binarySearch( ranges, - Range.upperBoundFn(), + Range::upperBound, range.lowerBound, KeyPresentBehavior.FIRST_AFTER, KeyAbsentBehavior.NEXT_HIGHER); @@ -439,7 +468,7 @@ private ImmutableList> intersectRanges(final Range range) { toIndex = SortedLists.binarySearch( ranges, - Range.lowerBoundFn(), + Range::lowerBound, range.upperBound, KeyPresentBehavior.FIRST_PRESENT, KeyAbsentBehavior.NEXT_HIGHER); @@ -470,6 +499,15 @@ public Range get(int index) { boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } }; } } @@ -482,7 +520,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 +539,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 +576,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 +599,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 +620,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 +663,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 +677,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 +696,7 @@ int indexOf(Object target) { @Override ImmutableSortedSet createDescendingSet() { - return new DescendingImmutableSortedSet(this); + return new DescendingImmutableSortedSet<>(this); } @Override @@ -672,9 +710,15 @@ 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 { @@ -703,7 +747,7 @@ boolean isPartialView() { /** Returns a new builder for an immutable range set. */ public static > Builder builder() { - return new Builder(); + return new Builder<>(); } /** @@ -772,7 +816,7 @@ Builder combine(Builder builder) { public ImmutableRangeSet build() { ImmutableList.Builder> mergedRangesBuilder = new ImmutableList.Builder<>(ranges.size()); - Collections.sort(ranges, Range.rangeLexOrdering()); + sort(ranges, Range.rangeLexOrdering()); PeekingIterator> peekingItr = Iterators.peekingIterator(ranges.iterator()); while (peekingItr.hasNext()) { Range range = peekingItr.next(); @@ -794,11 +838,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 +864,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..c004120a3232 100644 --- a/android/guava/src/com/google/common/collect/ImmutableSet.java +++ b/android/guava/src/com/google/common/collect/ImmutableSet.java @@ -20,14 +20,18 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.CollectPreconditions.checkNonnegative; 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.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 +39,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 @@ -46,6 +51,21 @@ @GwtCompatible(serializable = true, emulated = true) @SuppressWarnings("serial") // we're overriding default serialization public abstract class ImmutableSet extends ImmutableCollection implements Set { + + /** + * 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +78,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 +160,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 +198,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 +231,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. @@ -237,6 +259,11 @@ static int chooseTableSize(int setSize) { * @throws NullPointerException if any of {@code elements} is null * @since 7.0 (source-compatible since 2.0) */ + // This the best we could do to get copyOfEnumSet to compile in the mainline. + // The suppression also covers the cast to E[], discussed below. + // In the backport, we don't have those cases and thus don't need this suppression. + // We keep it to minimize diffs. + @SuppressWarnings("unchecked") public static ImmutableSet copyOf(Collection elements) { /* * TODO(lowasser): consider checking for ImmutableAsList here @@ -317,7 +344,7 @@ boolean isHashCodeFast() { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -340,7 +367,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() { @@ -359,6 +386,7 @@ ImmutableList createAsList() { * static factories. This is necessary to ensure that the existence of a * particular implementation type is an implementation detail. */ + @J2ktIncompatible // serialization private static class SerializedForm implements Serializable { final Object[] elements; @@ -374,16 +402,22 @@ Object readResolve() { } @Override + @J2ktIncompatible // serialization Object writeReplace() { return new SerializedForm(toArray()); } + @J2ktIncompatible // serialization + 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,10 +432,9 @@ 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); } /** @@ -423,7 +456,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 +467,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)]; + } } /** @@ -484,6 +519,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 +581,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 +600,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 +625,6 @@ public ImmutableSet build() { } } } + + 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..7b28da633180 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,19 +35,26 @@ 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 @@ -51,6 +62,91 @@ @GwtCompatible(serializable = true, emulated = true) 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: + * + *

    {@code
    +   * 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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: + * + *

    {@code
    +   * 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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,6 +225,19 @@ 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: @@ -157,9 +266,41 @@ public Builder() { super(); } + 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 +329,6 @@ public Builder put(Entry entry) { * @since 19.0 */ @CanIgnoreReturnValue - @Beta @Override public Builder putAll(Iterable> entries) { super.putAll(entries); @@ -205,7 +345,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 +398,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 +427,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 +454,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 +462,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 +480,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 +518,7 @@ static ImmutableSetMultimap fromMapEntries( ImmutableSetMultimap( ImmutableMap> map, int size, - @NullableDecl Comparator valueComparator) { + @Nullable Comparator valueComparator) { super(map, size); this.emptySet = emptySet(valueComparator); } @@ -362,13 +531,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 +572,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 +590,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 +610,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 +632,32 @@ public UnmodifiableIterator> iterator() { boolean isPartialView() { return false; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + 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); } private static ImmutableSet.Builder valuesBuilder( - @NullableDecl Comparator valueComparator) { + @Nullable Comparator valueComparator) { return (valueComparator == null) ? new ImmutableSet.Builder() : new ImmutableSortedSet.Builder(valueComparator); @@ -490,26 +668,29 @@ private static ImmutableSet.Builder valuesBuilder( * values for that key, and the key's values */ @GwtIncompatible // java.io.ObjectOutputStream + @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 + @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 + @J2ktIncompatible // Serialization type safety is at the caller's mercy. @SuppressWarnings("unchecked") private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { @@ -523,7 +704,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 +712,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 +724,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); } @@ -554,5 +735,6 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo } @GwtIncompatible // not needed in emulated source. + @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..7e23509bc16c 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,23 +54,67 @@ * 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 +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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 = + private static final ImmutableSortedMap, Object> NATURAL_EMPTY_MAP = new ImmutableSortedMap<>( ImmutableSortedSet.emptySet(Ordering.natural()), ImmutableList.of()); @@ -104,10 +157,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 +168,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 +179,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 +190,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 +401,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 +418,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 +476,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. @@ -298,22 +500,25 @@ private static ImmutableSortedMap fromEntries( private static ImmutableSortedMap fromEntries( final 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 +527,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,7 +587,7 @@ 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()); } /** @@ -391,35 +600,34 @@ public static , V> Builder reverseOrder() { * .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 +691,6 @@ public Builder putAll(Map map) { * @since 19.0 */ @CanIgnoreReturnValue - @Beta @Override public Builder putAll(Iterable> entries) { super.putAll(entries); @@ -497,7 +704,6 @@ public Builder putAll(Iterable> * @deprecated Unsupported by ImmutableSortedMap.Builder. */ @CanIgnoreReturnValue - @Beta @Override @Deprecated @DoNotCall("Always throws UnsupportedOperationException") @@ -517,19 +723,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 +767,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 +775,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 +787,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 transient @Nullable ImmutableSortedMap descendingMap; ImmutableSortedMap(RegularImmutableSortedSet keySet, ImmutableList valueList) { this(keySet, valueList, null); @@ -566,7 +818,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 +830,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); } @@ -620,6 +872,15 @@ boolean isPartialView() { public int size() { return ImmutableSortedMap.this.size(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } }; } @@ -627,6 +888,15 @@ public int size() { ImmutableMap map() { return ImmutableSortedMap.this; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } return isEmpty() ? ImmutableSet.>of() : new EntrySet(); } @@ -789,52 +1059,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 +1118,7 @@ public Entry lastEntry() { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final Entry pollFirstEntry() { + public final @Nullable Entry pollFirstEntry() { throw new UnsupportedOperationException(); } @@ -862,22 +1132,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,6 +1170,7 @@ public ImmutableSortedSet descendingKeySet() { * are reconstructed using public factory methods. This ensures that the implementation types * remain as implementation details. */ + @J2ktIncompatible // serialization private static class SerializedForm extends ImmutableMap.SerializedForm { private final Comparator comparator; @@ -915,11 +1188,310 @@ Builder makeBuilder(int size) { } @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; + + /** + * 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 + @SuppressWarnings("Java7ApiChecker") + @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 + @SuppressWarnings("Java7ApiChecker") + @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..c482de924017 100644 --- a/android/guava/src/com/google/common/collect/ImmutableSortedMultiset.java +++ b/android/guava/src/com/google/common/collect/ImmutableSortedMultiset.java @@ -18,11 +18,14 @@ 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.Arrays; import java.util.Collection; @@ -30,6 +33,10 @@ 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 +49,81 @@ * 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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())); + } + + @SuppressWarnings("Java7ApiChecker") + @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 +135,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 +148,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 +158,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 +168,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 +179,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 +190,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 +234,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 +252,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 +278,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) { @@ -252,7 +318,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 +326,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 +338,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 +352,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 +378,7 @@ public ImmutableSortedMultiset descendingMultiset() { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final Entry pollFirstEntry() { + public final @Nullable Entry pollFirstEntry() { throw new UnsupportedOperationException(); } @@ -328,7 +394,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 +424,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 +432,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,11 +446,11 @@ 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()); } /** @@ -639,6 +705,7 @@ public ImmutableSortedMultiset build() { } } + @J2ktIncompatible // serialization private static final class SerializedForm implements Serializable { final Comparator comparator; final E[] elements; @@ -669,7 +736,175 @@ 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 + @SuppressWarnings("Java7ApiChecker") + @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 + @SuppressWarnings("Java7ApiChecker") + @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(); + } + + 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..7085eb0b25b3 100644 --- a/android/guava/src/com/google/common/collect/ImmutableSortedSet.java +++ b/android/guava/src/com/google/common/collect/ImmutableSortedSet.java @@ -19,9 +19,12 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; 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 +38,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,7 +52,7 @@ * 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 @@ -57,13 +61,32 @@ // TODO(benyu): benchmark and optimize all creation paths, which are a mess now @GwtCompatible(serializable = true, emulated = true) @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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]; @@ -375,7 +395,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 +403,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,7 +413,7 @@ public static > Builder reverseOrder() { * implement {@link Comparable}. */ public static > Builder naturalOrder() { - return new Builder(Ordering.natural()); + return new Builder<>(Ordering.natural()); } /** @@ -420,10 +440,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 +539,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 +586,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 +611,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 +639,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 +658,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 +714,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 +730,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 +759,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,6 +775,7 @@ public ImmutableSortedSet descendingSet() { * only. This is necessary to ensure that the existence of a particular * implementation type is an implementation detail. */ + @J2ktIncompatible // serialization private static class SerializedForm implements Serializable { final Comparator comparator; final Object[] elements; @@ -742,12 +793,165 @@ Object readResolve() { 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 + @SuppressWarnings("Java7ApiChecker") + @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(); + } + + 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..234fb40afca1 100644 --- a/android/guava/src/com/google/common/collect/ImmutableTable.java +++ b/android/guava/src/com/google/common/collect/ImmutableTable.java @@ -17,25 +17,34 @@ 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.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 +53,49 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +141,7 @@ static ImmutableTable copyOf( for (Cell cell : cells) { builder.put(cell); } - return builder.build(); + return builder.buildOrThrow(); } /** @@ -105,7 +157,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")); @@ -121,7 +173,7 @@ static Cell cellOf(R rowKey, C columnKey, V value) { * .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 @@ -132,16 +184,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 @Nullable Comparator rowComparator; + private @Nullable Comparator columnComparator; /** * Creates a new builder. The returned builder is equivalent to the builder generated by {@link @@ -215,15 +267,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); } @@ -311,12 +378,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 +410,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 +437,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. @@ -432,7 +496,15 @@ Object readResolve() { private static final long serialVersionUID = 0; } - final Object writeReplace() { - return createSerializedForm(); + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + abstract Object writeReplace(); + + @GwtIncompatible // serialization + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); } + + 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..2ab6519c1517 100644 --- a/android/guava/src/com/google/common/collect/IndexedImmutableSet.java +++ b/android/guava/src/com/google/common/collect/IndexedImmutableSet.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; @GwtCompatible(emulated = true) abstract class IndexedImmutableSet extends ImmutableSet { @@ -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 // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } }; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } 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..93b0b509c4fb 100644 --- a/android/guava/src/com/google/common/collect/Interners.java +++ b/android/guava/src/com/google/common/collect/Interners.java @@ -16,13 +16,14 @@ 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 org.jspecify.annotations.Nullable; /** * Contains static methods pertaining to instances of {@link Interner}. @@ -30,7 +31,7 @@ * @author Kevin Bourrillion * @since 3.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class Interners { private Interners() {} @@ -81,7 +82,7 @@ public Interner build() { if (!strong) { mapMaker.weakKeys(); } - return new InternerImpl(mapMaker); + return new InternerImpl<>(mapMaker); } } @@ -124,11 +125,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,7 +159,7 @@ 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 { @@ -176,7 +181,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..c63763adf4e2 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,17 @@ import java.util.Queue; import java.util.RandomAccess; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +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,7 +54,7 @@ * absolutely necessary. * *

    See the Guava User Guide article on {@code + * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#iterables">{@code * Iterables}. * * @author Kevin Bourrillion @@ -65,7 +66,8 @@ public final class Iterables { private Iterables() {} /** Returns an unmodifiable view of {@code iterable}. */ - public static Iterable unmodifiableIterable(final Iterable iterable) { + public static Iterable unmodifiableIterable( + final Iterable iterable) { checkNotNull(iterable); if (iterable instanceof UnmodifiableIterable || iterable instanceof ImmutableCollection) { @SuppressWarnings("unchecked") // Since it's unmodifiable, the covariant cast is safe @@ -81,12 +83,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 +124,9 @@ 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) { + // instead of because of Kotlin b/189937072, discussed in Joiner. + 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(final 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( + final Iterable iterable, final 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( + final Iterable iterable, final 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,7 +607,7 @@ public Iterator> iterator() { * *

    {@code Stream} equivalent: {@link Stream#filter}. */ - public static Iterable filter( + public static Iterable filter( final Iterable unfiltered, final Predicate retainIfTrue) { checkNotNull(unfiltered); checkNotNull(retainIfTrue); @@ -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,7 +750,7 @@ public static int indexOf(Iterable iterable, Predicate predica * *

    {@code Stream} equivalent: {@link Stream#map} */ - public static Iterable transform( + public static Iterable transform( final Iterable fromIterable, final Function function) { checkNotNull(fromIterable); checkNotNull(function); @@ -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(); @@ -785,8 +828,9 @@ public static T get( * @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); } @@ -799,7 +843,8 @@ public static T getFirst(Iterable iterable, @NullableDecl T def * @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; @@ -823,21 +868,23 @@ public static T getLast(Iterable iterable) { * @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); } } 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 +907,8 @@ private static T getLastInNonemptyList(List list) { * * @since 3.0 */ - public static Iterable skip(final Iterable iterable, final int numberToSkip) { + public static Iterable skip( + final Iterable iterable, final int numberToSkip) { checkNotNull(iterable); checkArgument(numberToSkip >= 0, "number to skip cannot be negative"); @@ -890,6 +938,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { T result = iterator.next(); atStart = false; // not called if next() fails @@ -919,7 +968,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( + final Iterable iterable, final int limitSize) { checkNotNull(iterable); checkArgument(limitSize >= 0, "limit is negative"); return new FluentIterable() { @@ -934,10 +984,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 +999,8 @@ public Iterator iterator() { * @see Iterators#consumingIterator(Iterator) * @since 2.0 */ - public static Iterable consumingIterable(final Iterable iterable) { + public static Iterable consumingIterable( + final Iterable iterable) { checkNotNull(iterable); return new FluentIterable() { @@ -996,8 +1050,7 @@ public static boolean isEmpty(Iterable iterable) { * * @since 11.0 */ - @Beta - public static Iterable mergeSorted( + public static Iterable mergeSorted( final Iterable> iterables, final Comparator comparator) { checkNotNull(iterables, "iterables"); @@ -1007,20 +1060,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..4fb76a816d90 100644 --- a/android/guava/src/com/google/common/collect/Iterators.java +++ b/android/guava/src/com/google/common/collect/Iterators.java @@ -21,8 +21,11 @@ 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; @@ -32,6 +35,7 @@ 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 +45,11 @@ import java.util.Enumeration; import java.util.Iterator; import java.util.List; -import java.util.ListIterator; import java.util.NoSuchElementException; 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,7 +61,7 @@ * necessary. * *

    See the Guava User Guide section on {@code + * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#iterables">{@code * Iterators}. * * @author Kevin Bourrillion @@ -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()) { @@ -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,7 @@ 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) { + public static Enumeration asEnumeration(Iterator iterator) { checkNotNull(iterator); return new Enumeration() { @Override @@ -1112,6 +1168,7 @@ public boolean hasMoreElements() { } @Override + @ParametricNullness public T nextElement() { return iterator.next(); } @@ -1119,11 +1176,11 @@ public T nextElement() { } /** Implementation of PeekingIterator that avoids peeking unless necessary. */ - private static class PeekingImpl implements PeekingIterator { + private static class PeekingImpl implements PeekingIterator { private final Iterator iterator; private boolean hasPeeked; - @NullableDecl private E peekedElement; + private @Nullable E peekedElement; public PeekingImpl(Iterator iterator) { this.iterator = checkNotNull(iterator); @@ -1135,11 +1192,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 +1211,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); } } @@ -1197,7 +1258,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 +1267,7 @@ public static PeekingIterator peekingIterator(Iterator itera PeekingImpl peeking = (PeekingImpl) iterator; return peeking; } - return new PeekingImpl(iterator); + return new PeekingImpl<>(iterator); } /** @@ -1214,8 +1276,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); } @@ -1231,13 +1297,12 @@ public static PeekingIterator peekingIterator(PeekingIterator iterator * * @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,21 +1314,16 @@ 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 { + private static class MergingIterator extends UnmodifiableIterator { final Queue> queue; public MergingIterator( - Iterable> iterators, - final Comparator itemComparator) { + 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()); - } - }; + (PeekingIterator o1, PeekingIterator o2) -> + itemComparator.compare(o1.peek(), o2.peek()); queue = new PriorityQueue<>(2, heapComparator); @@ -1280,6 +1340,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { PeekingIterator nextIter = queue.remove(); T next = nextIter.next(); @@ -1290,9 +1351,9 @@ public T next() { } } - private static class ConcatenatedIterator implements Iterator { + private static 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 +1365,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 +1376,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 +1426,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { if (hasNext()) { toRemove = iterator; @@ -1377,14 +1438,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..2bbb0f10d9e1 100644 --- a/android/guava/src/com/google/common/collect/LexicographicalOrdering.java +++ b/android/guava/src/com/google/common/collect/LexicographicalOrdering.java @@ -20,11 +20,12 @@ 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 { +final class LexicographicalOrdering extends Ordering> + implements Serializable { final Comparator elementOrder; LexicographicalOrdering(Comparator elementOrder) { @@ -51,7 +52,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; } diff --git a/android/guava/src/com/google/common/collect/LinkedHashMultimap.java b/android/guava/src/com/google/common/collect/LinkedHashMultimap.java index 361661705d55..9f4b08914d8e 100644 --- a/android/guava/src/com/google/common/collect/LinkedHashMultimap.java +++ b/android/guava/src/com/google/common/collect/LinkedHashMultimap.java @@ -16,11 +16,13 @@ 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; @@ -36,7 +38,7 @@ 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; /** * Implementation of {@code Multimap} that does not allow duplicate key-value entries and that @@ -65,20 +67,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 +public final class LinkedHashMultimap extends LinkedHashMultimapGwtSerializationDependencies { /** 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 +97,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,14 +111,14 @@ 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 { + private interface ValueSetLink { ValueSetLink getPredecessorInValueSet(); ValueSetLink getSuccessorInValueSet(); @@ -121,21 +128,25 @@ private interface ValueSetLink { void setSuccessorInValueSet(ValueSetLink entry); } - private static void succeedsInValueSet(ValueSetLink pred, ValueSetLink succ) { + private static void succeedsInValueSet( + ValueSetLink pred, ValueSetLink succ) { pred.setSuccessorInValueSet(succ); succ.setPredecessorInValueSet(pred); } - private static void succeedsInMultimap(ValueEntry pred, ValueEntry succ) { + private static void succeedsInMultimap( + ValueEntry pred, ValueEntry succ) { pred.setSuccessorInMultimap(succ); succ.setPredecessorInMultimap(pred); } - private static void deleteFromValueSet(ValueSetLink entry) { + private static void deleteFromValueSet( + ValueSetLink entry) { succeedsInValueSet(entry.getPredecessorInValueSet(), entry.getSuccessorInValueSet()); } - private static void deleteFromMultimap(ValueEntry entry) { + private static void deleteFromMultimap( + ValueEntry entry) { succeedsInMultimap(entry.getPredecessorInMultimap(), entry.getSuccessorInMultimap()); } @@ -146,39 +157,69 @@ private static void deleteFromMultimap(ValueEntry entry) { * whole. */ @VisibleForTesting - static final class ValueEntry extends ImmutableEntry implements ValueSetLink { + static final class ValueEntry + extends ImmutableEntry implements ValueSetLink { final int smearedValueHash; - @NullableDecl ValueEntry nextInValueBucket; + @Nullable ValueEntry nextInValueBucket; + /* + * The *InValueSet and *InMultimap fields below are null after construction, but we almost + * always call succeedsIn*() to initialize them immediately thereafter. + * + * The exception is the *InValueSet fields of multimapHeaderEntry, which are never set. (That + * works out fine as long as we continue to be careful not to try to delete them or iterate + * past them.) + * + * We could consider "lying" and omitting @CheckNotNull from all these fields. Normally, I'm not + * a fan of that: What if we someday implement (presumably to be enabled during tests only) + * bytecode rewriting that checks for any null value that passes through an API with a + * known-non-null type? But that particular problem might not arise here, since we're not + * actually reading from the fields in any case in which they might be null (as proven by the + * requireNonNull checks below). Plus, we're *already* lying here, since newHeader passes a null + * key and value, which we pass to the superconstructor, even though the key and value type for + * a given entry might not include null. The right fix for the header problems is probably to + * define a separate MultimapLink interface with a separate "header" implementation, which + * hopefully could avoid implementing Entry or ValueSetLink at all. (But note that that approach + * requires us to define extra classes -- unfortunate under Android.) *Then* we could consider + * lying about the fields below on the grounds that we always initialize them just after the + * constructor -- an example of the kind of lying that our hypothetical bytecode rewriter would + * already have to deal with, thanks to DI frameworks that perform field and method injection, + * frameworks like Android that define post-construct hooks like Activity.onCreate, etc. + */ - @NullableDecl ValueSetLink predecessorInValueSet; - @NullableDecl ValueSetLink successorInValueSet; + private @Nullable ValueSetLink predecessorInValueSet; + private @Nullable ValueSetLink 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) { + @SuppressWarnings("nullness") // see the comment on the class fields, especially about newHeader + static ValueEntry newHeader() { + return new ValueEntry<>(null, null, 0, null); + } + + boolean matchesValue(@Nullable Object v, int smearedVHash) { return smearedValueHash == smearedVHash && Objects.equal(getValue(), v); } @Override public ValueSetLink getPredecessorInValueSet() { - return predecessorInValueSet; + return requireNonNull(predecessorInValueSet); // see the comment on the class fields } @Override public ValueSetLink getSuccessorInValueSet() { - return successorInValueSet; + return requireNonNull(successorInValueSet); // see the comment on the class fields } @Override @@ -192,11 +233,11 @@ public void setSuccessorInValueSet(ValueSetLink entry) { } public ValueEntry getPredecessorInMultimap() { - return predecessorInMultimap; + return requireNonNull(predecessorInMultimap); // see the comment on the class fields } public ValueEntry getSuccessorInMultimap() { - return successorInMultimap; + return requireNonNull(successorInMultimap); // see the comment on the class fields } public void setSuccessorInMultimap(ValueEntry multimapSuccessor) { @@ -220,7 +261,7 @@ private LinkedHashMultimap(int keyCapacity, int valueSetCapacity) { checkNonnegative(valueSetCapacity, "expectedValuesPerKey"); this.valueSetCapacity = valueSetCapacity; - this.multimapHeaderEntry = new ValueEntry<>(null, null, 0, null); + this.multimapHeaderEntry = ValueEntry.newHeader(); succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); } @@ -246,7 +287,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 +300,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); } @@ -314,8 +355,8 @@ final class ValueSet extends Sets.ImprovedAbstractSet implements ValueSetLink * 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; @@ -324,15 +365,15 @@ final class ValueSet extends Sets.ImprovedAbstractSet implements ValueSetLink private ValueSetLink firstEntry; private ValueSetLink 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; } @@ -364,7 +405,7 @@ public void setSuccessorInValueSet(ValueSetLink entry) { public Iterator iterator() { return new Iterator() { ValueSetLink nextEntry = firstEntry; - @NullableDecl ValueEntry toRemove; + @Nullable ValueEntry toRemove; int expectedModCount = modCount; private void checkForComodification() { @@ -380,6 +421,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public V next() { if (!hasNext()) { throw new NoSuchElementException(); @@ -394,7 +436,7 @@ public V next() { @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 +450,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 +463,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]; @@ -446,7 +488,8 @@ 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; @@ -462,7 +505,7 @@ private void rehashIfNecessary() { @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; @@ -504,8 +547,8 @@ public void clear() { @Override Iterator> entryIterator() { return new Iterator>() { - ValueEntry nextEntry = multimapHeaderEntry.successorInMultimap; - @NullableDecl ValueEntry toRemove; + ValueEntry nextEntry = multimapHeaderEntry.getSuccessorInMultimap(); + @Nullable ValueEntry toRemove; @Override public boolean hasNext() { @@ -519,13 +562,13 @@ public Entry next() { } ValueEntry result = nextEntry; toRemove = result; - nextEntry = nextEntry.successorInMultimap; + nextEntry = nextEntry.getSuccessorInMultimap(); return result; } @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; } @@ -548,6 +591,7 @@ public void clear() { * and the entries in order */ @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeInt(keySet().size()); @@ -562,9 +606,10 @@ private void writeObject(ObjectOutputStream stream) throws IOException { } @GwtIncompatible // java.io.ObjectInputStream + @J2ktIncompatible private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - multimapHeaderEntry = new ValueEntry<>(null, null, 0, null); + multimapHeaderEntry = ValueEntry.newHeader(); succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY; int distinctKeys = stream.readInt(); @@ -580,11 +625,16 @@ 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 + @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 index bb4a2e490e45..8c91683d19c0 100644 --- a/android/guava/src/com/google/common/collect/LinkedHashMultimapGwtSerializationDependencies.java +++ b/android/guava/src/com/google/common/collect/LinkedHashMultimapGwtSerializationDependencies.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.Collection; import java.util.Map; +import org.jspecify.annotations.Nullable; /** * A dummy superclass to support GWT serialization of the element types of a {@link @@ -30,7 +31,8 @@ *

    TODO(cpovirk): Consider applying this subclass approach to our other types. */ @GwtCompatible(emulated = true) -abstract class LinkedHashMultimapGwtSerializationDependencies +abstract class LinkedHashMultimapGwtSerializationDependencies< + K extends @Nullable Object, V extends @Nullable Object> 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..65761c99eea1 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 { +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,7 @@ public static LinkedHashMultiset create(Iterable elements) { } @Override - void init(int distinctElements) { - backingMap = new ObjectCountLinkedHashMap<>(distinctElements); + ObjectCountHashMap newBackingMap(int distinctElements) { + return new ObjectCountLinkedHashMap<>(distinctElements); } } diff --git a/android/guava/src/com/google/common/collect/LinkedListMultimap.java b/android/guava/src/com/google/common/collect/LinkedListMultimap.java index 3aa1efdabe36..014d1af55ca8 100644 --- a/android/guava/src/com/google/common/collect/LinkedListMultimap.java +++ b/android/guava/src/com/google/common/collect/LinkedListMultimap.java @@ -18,11 +18,12 @@ 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.WeakOuter; import java.io.IOException; @@ -39,7 +40,7 @@ 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 @@ -87,15 +88,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 Mike Bostock * @since 2.0 */ @GwtCompatible(serializable = true, emulated = true) -public class LinkedListMultimap extends AbstractMultimap - implements ListMultimap, Serializable { +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 +103,42 @@ 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 AbstractMapEntry { + @ParametricNullness final K key; + @ParametricNullness V value; + @Nullable Node next; // the next node (with any key) + @Nullable Node previous; // the previous node (with any key) + @Nullable Node nextSibling; // the next node with the same key + @Nullable Node previousSibling; // the previous node with the same key - Node(@NullableDecl K key, @NullableDecl V value) { + Node(@ParametricNullness K key, @ParametricNullness V value) { this.key = key; this.value = value; } @Override + @ParametricNullness public K getKey() { return key; } @Override + @ParametricNullness public V getValue() { return value; } @Override - public V setValue(@NullableDecl V newValue) { + @ParametricNullness + public V setValue(@ParametricNullness V newValue) { V result = value; this.value = newValue; return result; } } - private static class KeyList { + private static class KeyList { Node head; Node tail; int count; @@ -148,8 +152,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 +165,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 +177,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 +189,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 +210,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 +237,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 +281,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.key)); 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.key)); 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 +312,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> { 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 +358,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 +370,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 +392,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,7 +421,7 @@ public void add(Entry e) { throw new UnsupportedOperationException(); } - void setValue(V value) { + void setValue(@ParametricNullness V value) { checkState(current != null); current.value = value; } @@ -413,8 +430,8 @@ void setValue(V 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; + @Nullable Node next = head; + @Nullable Node current; int expectedModCount = modCount; private void checkForConcurrentModification() { @@ -430,9 +447,12 @@ public boolean hasNext() { } @Override + @ParametricNullness public K next() { checkForConcurrentModification(); - checkElement(next); + if (next == null) { + throw new NoSuchElementException(); + } current = next; seenKeys.add(current.key); do { // skip ahead to next unseen key @@ -444,7 +464,7 @@ public K next() { @Override public void remove() { checkForConcurrentModification(); - checkRemove(current != null); + checkState(current != null, "no calls to next() since the last call to remove()"); removeAllNodes(current.key); current = null; expectedModCount = modCount; @@ -453,14 +473,14 @@ public void remove() { /** A {@code ListIterator} over values for a specified key. */ private class ValueForKeyIterator implements ListIterator { - @NullableDecl final Object key; + @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 +494,7 @@ private class ValueForKeyIterator implements ListIterator { * * @throws IndexOutOfBoundsException if index is invalid */ - public ValueForKeyIterator(@NullableDecl Object key, int index) { + public ValueForKeyIterator(@ParametricNullness K key, int index) { KeyList keyList = keyToKeyList.get(key); int size = (keyList == null) ? 0 : keyList.count; checkPositionIndex(index, size); @@ -501,8 +521,11 @@ public boolean hasNext() { @CanIgnoreReturnValue @Override + @ParametricNullness public V next() { - checkElement(next); + if (next == null) { + throw new NoSuchElementException(); + } previous = current = next; next = next.nextSibling; nextIndex++; @@ -516,8 +539,11 @@ public boolean hasPrevious() { @CanIgnoreReturnValue @Override + @ParametricNullness public V previous() { - checkElement(previous); + if (previous == null) { + throw new NoSuchElementException(); + } next = current = previous; previous = previous.previousSibling; nextIndex--; @@ -536,7 +562,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 +574,14 @@ public void remove() { } @Override - public void set(V value) { + public void set(@ParametricNullness V value) { checkState(current != null); current.value = 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 +600,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 +620,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 +637,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 +662,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 +673,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 +707,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 final K key) { return new AbstractSequentialList() { @Override public int size() { @@ -705,12 +737,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(); } } @@ -749,12 +781,13 @@ public ListIterator listIterator(int index) { final 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); } }; @@ -817,6 +850,7 @@ Map> createAsMap() { * from the entries() ordering */ @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeInt(size()); @@ -827,6 +861,7 @@ private void writeObject(ObjectOutputStream stream) throws IOException { } @GwtIncompatible // java.io.ObjectInputStream + @J2ktIncompatible private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); keyToKeyList = CompactLinkedHashMap.create(); @@ -841,5 +876,6 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo } @GwtIncompatible // java serialization not supported + @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..7f252d519d6c 100644 --- a/android/guava/src/com/google/common/collect/Lists.java +++ b/android/guava/src/com/google/common/collect/Lists.java @@ -24,10 +24,12 @@ 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; @@ -48,14 +50,14 @@ import java.util.NoSuchElementException; 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 @@ -73,12 +75,15 @@ private Lists() {} * *

    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<>(); } @@ -98,7 +103,8 @@ public static ArrayList newArrayList() { */ @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 +121,16 @@ 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) @@ -137,7 +146,9 @@ public static ArrayList newArrayList(Iterable elements) { * ImmutableList#copyOf(Iterator)} instead. */ @GwtCompatible(serializable = true) - public static ArrayList newArrayList(Iterator elements) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static ArrayList newArrayList( + Iterator elements) { ArrayList list = newArrayList(); Iterators.addAll(list, elements); return list; @@ -155,11 +166,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") @@ -168,7 +180,9 @@ static int computeArrayListCapacity(int arraySize) { * @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); } @@ -187,7 +201,9 @@ public static ArrayList newArrayListWithCapacity(int initialArraySize) { * @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)); } @@ -203,13 +219,15 @@ 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 + public static LinkedList newLinkedList() { return new LinkedList<>(); } @@ -225,13 +243,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) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static LinkedList newLinkedList( + Iterable elements) { LinkedList list = newLinkedList(); Iterables.addAll(list, elements); return list; @@ -246,8 +267,9 @@ public static LinkedList newLinkedList(Iterable elements) { * @return a new, empty {@code CopyOnWriteArrayList} * @since 12.0 */ + @J2ktIncompatible @GwtIncompatible // CopyOnWriteArrayList - public static CopyOnWriteArrayList newCopyOnWriteArrayList() { + public static CopyOnWriteArrayList newCopyOnWriteArrayList() { return new CopyOnWriteArrayList<>(); } @@ -258,8 +280,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 +307,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 +327,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 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 +351,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; + @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 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 +382,7 @@ public int size() { } @Override + @ParametricNullness public E get(int index) { switch (index) { case 0: @@ -366,7 +396,7 @@ public E get(int index) { } } - private static final long serialVersionUID = 0; + @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -514,11 +544,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 +560,9 @@ public static List transform( * * @see Lists#transform */ - private static class TransformingSequentialList extends AbstractSequentialList - implements Serializable { + private static class TransformingSequentialList< + F extends @Nullable Object, T extends @Nullable Object> + extends AbstractSequentialList implements Serializable { final List fromList; final Function function; @@ -545,8 +576,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 @@ -554,11 +585,17 @@ public int size() { return fromList.size(); } + @Override + public boolean isEmpty() { + return fromList.isEmpty(); + } + @Override public ListIterator listIterator(final int index) { return new TransformedListIterator(fromList.listIterator(index)) { @Override - T transform(F from) { + @ParametricNullness + T transform(@ParametricNullness F from) { return function.apply(from); } }; @@ -574,8 +611,9 @@ T transform(F from) { * * @see Lists#transform */ - private static class TransformingRandomAccessList extends AbstractList - implements RandomAccess, Serializable { + private static class TransformingRandomAccessList< + F extends @Nullable Object, T extends @Nullable Object> + extends AbstractList implements RandomAccess, Serializable { final List fromList; final Function function; @@ -584,12 +622,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)); } @@ -642,7 +685,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 +693,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 +706,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 +721,8 @@ public boolean isEmpty() { } } - private static class RandomAccessPartition extends Partition implements RandomAccess { + private static class RandomAccessPartition extends Partition + implements RandomAccess { RandomAccessPartition(List list, int size) { super(list, size); } @@ -702,7 +746,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 +760,12 @@ private static final class StringAsImmutableList extends ImmutableList { @@ -779,9 +831,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 +847,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 +871,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 +881,7 @@ public void clear() { } @Override + @ParametricNullness public T remove(int index) { return forwardList.remove(reverseIndex(index)); } @@ -835,11 +892,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)); } @@ -869,7 +928,7 @@ public ListIterator listIterator(int index) { boolean canRemoveOrSet; @Override - public void add(T e) { + public void add(@ParametricNullness T e) { forwardIterator.add(e); forwardIterator.previous(); canRemoveOrSet = false; @@ -886,6 +945,7 @@ public boolean hasPrevious() { } @Override + @ParametricNullness public T next() { if (!hasNext()) { throw new NoSuchElementException(); @@ -900,6 +960,7 @@ public int nextIndex() { } @Override + @ParametricNullness public T previous() { if (!hasPrevious()) { throw new NoSuchElementException(); @@ -921,7 +982,7 @@ public void remove() { } @Override - public void set(T e) { + public void set(@ParametricNullness T e) { checkState(canRemoveOrSet); forwardIterator.set(e); } @@ -929,7 +990,8 @@ public void set(T e) { } } - private static class RandomAccessReverseList extends ReverseList implements RandomAccess { + private static class RandomAccessReverseList extends ReverseList + implements RandomAccess { RandomAccessReverseList(List forwardList) { super(forwardList); } @@ -949,7 +1011,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; } @@ -970,12 +1032,13 @@ static boolean equalsImpl(List thisList, @NullableDecl Object other) { } 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,7 +1049,7 @@ 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 { @@ -1000,7 +1063,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,7 +1082,7 @@ 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 { @@ -1033,7 +1096,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 +1114,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( + final List list, int fromIndex, int toIndex) { List wrapper; if (list instanceof RandomAccess) { wrapper = @@ -1066,7 +1130,7 @@ public ListIterator listIterator(int index) { return backingList.listIterator(index); } - private static final long serialVersionUID = 0; + @J2ktIncompatible private static final long serialVersionUID = 0; }; } else { wrapper = @@ -1076,13 +1140,13 @@ public ListIterator listIterator(int index) { return backingList.listIterator(index); } - private static final long serialVersionUID = 0; + @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 +1154,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 +1164,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 +1192,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..b8044d070683 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,7 +67,7 @@ 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 @@ -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..a8a0f09e7b7f 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 @@ -85,6 +86,7 @@ * @author Kevin Bourrillion * @since 2.0 */ +@J2ktIncompatible @GwtCompatible(emulated = true) public final class MapMaker { private static final int DEFAULT_INITIAL_CAPACITY = 16; @@ -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 diff --git a/android/guava/src/com/google/common/collect/MapMakerInternalMap.java b/android/guava/src/com/google/common/collect/MapMakerInternalMap.java index b72d0ea54371..984c42fbb6b9 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; @@ -36,7 +40,6 @@ import java.util.AbstractCollection; import java.util.AbstractMap; import java.util.AbstractSet; -import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.Map; @@ -47,7 +50,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,8 +68,13 @@ * @author Doug Lea ({@code ConcurrentHashMap}) */ // TODO(kak): Consider removing @CanIgnoreReturnValue from this class. +@J2ktIncompatible @GwtIncompatible -@SuppressWarnings("GuardedBy") // TODO(b/35466881): Fix or suppress. +@SuppressWarnings({ + "GuardedBy", // TODO(b/35466881): Fix or suppress. + "nullness", // too much trouble for the payoff +}) +@NullUnmarked // TODO(cpovirk): Annotate for nullness. class MapMakerInternalMap< K, V, @@ -126,8 +135,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 +165,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 +196,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); } } @@ -286,18 +293,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 +346,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 +376,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 +385,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; - StrongKeyStrongValueEntry copy(StrongKeyStrongValueEntry newNext) { - StrongKeyStrongValueEntry newEntry = - new StrongKeyStrongValueEntry<>(this.key, this.hash, newNext); - newEntry.value = this.value; - return newEntry; + LinkedStrongKeyStrongValueEntry(K key, int hash, StrongKeyStrongValueEntry next) { + super(key, hash); + this.next = next; + } + + @Override + public StrongKeyStrongValueEntry getNext() { + return next; + } } /** Concrete implementation of {@link InternalEntryHelper} for strong keys and strong values. */ @@ -438,17 +440,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 +460,7 @@ public void setValue( StrongKeyStrongValueSegment segment, StrongKeyStrongValueEntry entry, V value) { - entry.setValue(value); + entry.value = value; } @Override @@ -464,49 +468,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 +537,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 +567,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 + 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; + + LinkedStrongKeyDummyValueEntry(K key, int hash, StrongKeyDummyValueEntry next) { + super(key, hash); + this.next = next; + } - StrongKeyDummyValueEntry copy(StrongKeyDummyValueEntry newNext) { - return new StrongKeyDummyValueEntry(this.key, this.hash, newNext); + @Override + public StrongKeyDummyValueEntry getNext() { + return next; + } } /** @@ -614,17 +632,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 +653,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 +665,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 + 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 +743,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 +769,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 +833,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 +863,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 + 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 +932,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 +967,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 +981,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 +991,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 +1028,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 +1042,7 @@ public DummyInternalEntry getEntry() { public void clear() {} @Override - public Object get() { + public @Nullable Object get() { return null; } @@ -1111,15 +1140,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 +1157,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 +1222,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 +1230,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 +1256,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 +1276,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 +1322,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 +1332,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 +1343,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 +1401,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 +1409,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 +1431,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 +1494,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 +1667,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 +1709,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 +1822,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 +1965,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 +2022,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 +2033,8 @@ StrongKeyStrongValueSegment self() { @SuppressWarnings("unchecked") @Override - public StrongKeyStrongValueEntry castForTesting(InternalEntry entry) { + public @Nullable StrongKeyStrongValueEntry castForTesting( + @Nullable InternalEntry entry) { return (StrongKeyStrongValueEntry) entry; } } @@ -2022,14 +2042,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 +2063,8 @@ ReferenceQueue getValueReferenceQueueForTesting() { @SuppressWarnings("unchecked") @Override - public StrongKeyWeakValueEntry castForTesting(InternalEntry entry) { + public @Nullable StrongKeyWeakValueEntry castForTesting( + @Nullable InternalEntry entry) { return (StrongKeyWeakValueEntry) entry; } @@ -2090,9 +2110,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 +2129,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 +2168,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 +2194,8 @@ ReferenceQueue getValueReferenceQueueForTesting() { @SuppressWarnings("unchecked") @Override - public WeakKeyWeakValueEntry castForTesting(InternalEntry entry) { + public @Nullable WeakKeyWeakValueEntry castForTesting( + @Nullable InternalEntry entry) { return (WeakKeyWeakValueEntry) entry; } @@ -2221,13 +2239,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 +2278,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 +2352,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 +2364,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 +2373,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 +2382,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; } @@ -2404,7 +2421,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 +2430,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 +2446,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 +2456,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 +2466,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 +2478,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 +2492,7 @@ public void clear() { } } - @NullableDecl transient Set keySet; + @LazyInit transient @Nullable Set keySet; @Override public Set keySet() { @@ -2483,7 +2500,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 +2508,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 +2522,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; @@ -2649,7 +2666,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; @@ -2681,7 +2698,7 @@ public Entry next() { } @WeakOuter - final class KeySet extends SafeToArraySet { + final class KeySet extends AbstractSet { @Override public Iterator iterator() { @@ -2741,23 +2758,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 +2809,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 +2823,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 +2873,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 +2885,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 +2921,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..14e3e892edb1 100644 --- a/android/guava/src/com/google/common/collect/Maps.java +++ b/android/guava/src/com/google/common/collect/Maps.java @@ -21,10 +21,16 @@ 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.Maps.immutableEntry; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static com.google.common.collect.Sets.newHashSet; +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; @@ -35,6 +41,7 @@ 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; @@ -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 @@ -81,45 +91,47 @@ public final class Maps { private Maps() {} - private enum EntryFunction implements Function, Object> { + private enum EntryFunction implements Function, @Nullable Object> { KEY { @Override - @NullableDecl - public Object apply(Entry entry) { + public @Nullable Object apply(Entry entry) { return entry.getKey(); } }, VALUE { @Override - @NullableDecl - public Object apply(Entry entry) { + public @Nullable Object apply(Entry entry) { return entry.getValue(); } }; } @SuppressWarnings("unchecked") - static Function, K> keyFunction() { + static Function, K> keyFunction() { return (Function) EntryFunction.KEY; } @SuppressWarnings("unchecked") - static Function, V> valueFunction() { + 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(); } @@ -153,9 +165,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 +177,50 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +228,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 +248,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 +273,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 +289,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 +311,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 +330,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 +356,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 +377,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 +399,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 +421,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 +446,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 +499,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,10 +524,11 @@ 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(); @@ -459,8 +556,9 @@ 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()); @@ -469,14 +567,15 @@ public static SortedMapDifference difference( onlyOnRight.putAll(right); // will whittle it down SortedMap onBoth = Maps.newTreeMap(comparator); SortedMap> differences = Maps.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, @@ -485,7 +584,17 @@ private static void doDifference( 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 +606,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 +615,8 @@ private static Map unmodifiableMap(Map map) { } } - static class MapDifferenceImpl implements MapDifference { + static class MapDifferenceImpl + implements MapDifference { final Map onlyOnLeft; final Map onlyOnRight; final Map onBoth; @@ -548,7 +659,7 @@ public Map> entriesDiffering() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -588,31 +699,35 @@ public String toString() { } } - static class ValueDifferenceImpl implements MapDifference.ValueDifference { - @NullableDecl private final V left; - @NullableDecl private final V right; + static class ValueDifferenceImpl + implements MapDifference.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) { + public boolean equals(@Nullable Object object) { if (object instanceof MapDifference.ValueDifference) { MapDifference.ValueDifference that = (MapDifference.ValueDifference) object; return Objects.equal(this.left, that.leftValue()) @@ -632,8 +747,8 @@ public String toString() { } } - static class SortedMapDifferenceImpl extends MapDifferenceImpl - implements SortedMapDifference { + static class SortedMapDifferenceImpl + extends MapDifferenceImpl implements SortedMapDifference { SortedMapDifferenceImpl( SortedMap onlyOnLeft, SortedMap onlyOnRight, @@ -669,7 +784,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 +816,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 +844,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 +873,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 +909,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 +925,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; @@ -839,17 +958,18 @@ public Iterator> iterator() { } } - static Iterator> asMapEntryIterator( - Set set, final Function function) { + static + Iterator> asMapEntryIterator(Set set, final Function function) { return new TransformedIterator>(set.iterator()) { @Override - Entry transform(final K key) { + Entry transform(@ParametricNullness final K key) { return immutableEntry(key, function.apply(key)); } }; } - private static class SortedAsMapView extends AsMapView implements SortedMap { + private static class SortedAsMapView + extends AsMapView implements SortedMap { SortedAsMapView(SortedSet set, Function function) { super(set, function); @@ -861,7 +981,7 @@ SortedSet backingSet() { } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return backingSet().comparator(); } @@ -871,33 +991,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 +1037,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 +1101,7 @@ public NavigableMap descendingMap() { } } - private static Set removeOnlySet(final Set set) { + private static Set removeOnlySet(final Set set) { return new ForwardingSet() { @Override protected Set delegate() { @@ -983,7 +1109,7 @@ protected Set delegate() { } @Override - public boolean add(E element) { + public boolean add(@ParametricNullness E element) { throw new UnsupportedOperationException(); } @@ -994,7 +1120,8 @@ public boolean addAll(Collection es) { }; } - private static SortedSet removeOnlySortedSet(final SortedSet set) { + private static SortedSet removeOnlySortedSet( + final SortedSet set) { return new ForwardingSortedSet() { @Override protected SortedSet delegate() { @@ -1002,7 +1129,7 @@ protected SortedSet delegate() { } @Override - public boolean add(E element) { + public boolean add(@ParametricNullness E element) { throw new UnsupportedOperationException(); } @@ -1012,24 +1139,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( + final NavigableSet set) { return new ForwardingNavigableSet() { @Override protected NavigableSet delegate() { @@ -1037,7 +1166,7 @@ protected NavigableSet delegate() { } @Override - public boolean add(E element) { + public boolean add(@ParametricNullness E element) { throw new UnsupportedOperationException(); } @@ -1047,34 +1176,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 +1230,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: + * + *

    {@code
    +   * 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 +1267,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(); } /** @@ -1142,14 +1287,26 @@ public static ImmutableMap toMap( * ... * 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: + * + *

    {@code
    +   * 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 +1319,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); } @@ -1198,14 +1360,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 +1386,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(); } /** @@ -1248,7 +1441,8 @@ public static ImmutableMap fromProperties(Properties properties) * @param value the value to be associated with the returned entry */ @GwtCompatible(serializable = true) - public static Entry immutableEntry(@NullableDecl K key, @NullableDecl V value) { + public static Entry immutableEntry( + @ParametricNullness K key, @ParametricNullness V value) { return new ImmutableEntry<>(key, value); } @@ -1260,36 +1454,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( + final 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( + final Iterator> entryIterator) { return new UnmodifiableIterator>() { @Override public boolean hasNext() { @@ -1303,8 +1502,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 +1524,26 @@ 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)}. */ + static class UnmodifiableEntrySet + extends UnmodifiableEntries implements Set> { UnmodifiableEntrySet(Set> entries) { super(entries); } @@ -1344,7 +1551,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); } @@ -1392,7 +1599,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); @@ -1443,7 +1650,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 +1667,22 @@ 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 class UnmodifiableBiMap + 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 +1694,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(); } @@ -1536,8 +1748,9 @@ 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) { + public static < + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + Map transformValues(Map fromMap, Function function) { return transformEntries(fromMap, asEntryTransformer(function)); } @@ -1577,8 +1790,10 @@ public static Map transformValues( * * @since 11.0 */ - public static SortedMap transformValues( - SortedMap fromMap, Function function) { + public static < + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + SortedMap transformValues( + SortedMap fromMap, Function function) { return transformEntries(fromMap, asEntryTransformer(function)); } @@ -1621,8 +1836,10 @@ public static SortedMap transformValues( * @since 13.0 */ @GwtIncompatible // NavigableMap - public static NavigableMap transformValues( - NavigableMap fromMap, Function function) { + public static < + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + NavigableMap transformValues( + NavigableMap fromMap, Function function) { return transformEntries(fromMap, asEntryTransformer(function)); } @@ -1673,8 +1890,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); } @@ -1725,8 +1944,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); } @@ -1779,8 +2000,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,7 +2016,8 @@ 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: @@ -1808,38 +2032,44 @@ public interface EntryTransformer { * @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); + @ParametricNullness + V2 transformEntry(@ParametricNullness K key, @ParametricNullness V1 value); } /** Views a function as an entry transformer that ignores the entry key. */ - static EntryTransformer asEntryTransformer( - final Function function) { + static + EntryTransformer asEntryTransformer(final Function function) { checkNotNull(function); return new EntryTransformer() { @Override - public V2 transformEntry(K key, V1 value) { + @ParametricNullness + public V2 transformEntry(@ParametricNullness K key, @ParametricNullness V1 value) { return function.apply(value); } }; } - static Function asValueToValueFunction( - final EntryTransformer transformer, final K key) { + static + Function asValueToValueFunction( + final EntryTransformer transformer, @ParametricNullness final K key) { checkNotNull(transformer); return new Function() { @Override - public V2 apply(@NullableDecl V1 v1) { + @ParametricNullness + public V2 apply(@ParametricNullness 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) { + static + Function, V2> asEntryToValueFunction( + final EntryTransformer transformer) { checkNotNull(transformer); return new Function, V2>() { @Override + @ParametricNullness public V2 apply(Entry entry) { return transformer.transformEntry(entry.getKey(), entry.getValue()); } @@ -1847,17 +2077,20 @@ public V2 apply(Entry entry) { } /** Returns a view of an entry transformed by the specified transformer. */ - static Entry transformEntry( - final EntryTransformer transformer, final Entry entry) { + static + Entry transformEntry( + final EntryTransformer transformer, final 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,8 +2098,9 @@ 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( + final EntryTransformer transformer) { checkNotNull(transformer); return new Function, Entry>() { @Override @@ -1876,7 +2110,9 @@ public Entry apply(final Entry entry) { }; } - static class TransformedEntriesMap extends IteratorBasedAbstractMap { + 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 +2128,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; } @@ -1937,8 +2176,9 @@ public Collection values() { } } - static class TransformedEntriesSortedMap extends TransformedEntriesMap - implements SortedMap { + 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 +2190,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 class TransformedEntriesNavigableMap< + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> extends TransformedEntriesSortedMap implements NavigableMap { TransformedEntriesNavigableMap( @@ -1990,12 +2233,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 +2253,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 +2308,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,11 +2352,13 @@ protected NavigableMap fromMap() { } } - static Predicate> keyPredicateOnEntries(Predicate keyPredicate) { + static Predicate> keyPredicateOnEntries( + Predicate keyPredicate) { return compose(keyPredicate, Maps.keyFunction()); } - static Predicate> valuePredicateOnEntries(Predicate valuePredicate) { + static Predicate> valuePredicateOnEntries( + Predicate valuePredicate) { return compose(valuePredicate, Maps.valueFunction()); } @@ -2138,7 +2385,7 @@ 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( + public static Map filterKeys( Map unfiltered, final Predicate keyPredicate) { checkNotNull(keyPredicate); Predicate> entryPredicate = keyPredicateOnEntries(keyPredicate); @@ -2173,7 +2420,7 @@ public static Map filterKeys( * * @since 11.0 */ - public static SortedMap filterKeys( + public static SortedMap filterKeys( SortedMap unfiltered, final Predicate keyPredicate) { // TODO(lowasser): Return a subclass of Maps.FilteredKeyMap for slightly better // performance. @@ -2207,8 +2454,9 @@ public static SortedMap filterKeys( * @since 14.0 */ @GwtIncompatible // NavigableMap - public static NavigableMap filterKeys( - NavigableMap unfiltered, final Predicate keyPredicate) { + 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)); @@ -2238,7 +2486,7 @@ public static NavigableMap filterKeys( * * @since 14.0 */ - public static BiMap filterKeys( + public static BiMap filterKeys( BiMap unfiltered, final Predicate keyPredicate) { checkNotNull(keyPredicate); return filterEntries(unfiltered, Maps.keyPredicateOnEntries(keyPredicate)); @@ -2267,7 +2515,7 @@ 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( + public static Map filterValues( Map unfiltered, final Predicate valuePredicate) { return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); } @@ -2298,8 +2546,9 @@ public static Map filterValues( * * @since 11.0 */ - public static SortedMap filterValues( - SortedMap unfiltered, final Predicate valuePredicate) { + public static + SortedMap filterValues( + SortedMap unfiltered, final Predicate valuePredicate) { return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); } @@ -2330,8 +2579,9 @@ public static SortedMap filterValues( * @since 14.0 */ @GwtIncompatible // NavigableMap - public static NavigableMap filterValues( - NavigableMap unfiltered, final Predicate valuePredicate) { + public static + NavigableMap filterValues( + NavigableMap unfiltered, final Predicate valuePredicate) { return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); } @@ -2362,7 +2612,7 @@ public static NavigableMap filterValues( * * @since 14.0 */ - public static BiMap filterValues( + public static BiMap filterValues( BiMap unfiltered, final Predicate valuePredicate) { return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); } @@ -2391,7 +2641,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 +2675,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 +2711,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 +2747,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,7 +2760,7 @@ 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)); @@ -2518,8 +2770,9 @@ private static Map filterFiltered( * Support {@code clear()}, {@code removeAll()}, and {@code retainAll()} when filtering a filtered * sorted map. */ - private static SortedMap filterFiltered( - FilteredEntrySortedMap 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,8 +2782,9 @@ private static SortedMap filterFiltered( * navigable map. */ @GwtIncompatible // NavigableMap - private static NavigableMap filterFiltered( - FilteredEntryNavigableMap map, Predicate> entryPredicate) { + private static + NavigableMap filterFiltered( + FilteredEntryNavigableMap map, Predicate> entryPredicate) { Predicate> predicate = Predicates.>and(map.entryPredicate, entryPredicate); return new FilteredEntryNavigableMap<>(map.unfiltered, predicate); @@ -2540,13 +2794,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) { + 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 +2812,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 +2835,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 +2851,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 +2861,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 Maps.Values { final Map unfiltered; final Predicate> predicate; @@ -2616,7 +2875,7 @@ 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(); @@ -2657,18 +2916,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 class FilteredKeyMap + extends AbstractFilteredMap { final Predicate keyPredicate; FilteredKeyMap( @@ -2693,12 +2954,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 { + static class FilteredEntryMap + extends AbstractFilteredMap { /** * Entries in this set satisfy the predicate, but they don't validate the input to {@code * Entry.setValue()}. @@ -2734,7 +2996,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 +3012,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 +3026,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 +3047,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 +3066,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 class FilteredEntrySortedMap< + K extends @Nullable Object, V extends @Nullable Object> + extends FilteredEntryMap implements SortedMap { FilteredEntrySortedMap( SortedMap unfiltered, Predicate> entryPredicate) { @@ -2840,54 +3105,60 @@ SortedSet createKeySet() { @WeakOuter 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 +3166,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 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,7 +3203,7 @@ private static class FilteredEntryNavigableMap extends AbstractNavigableMa } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return unfiltered.comparator(); } @@ -2975,23 +3248,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 +3283,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,32 +3299,37 @@ 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) { + 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())); + return forwardPredicate.apply( + Maps.immutableEntry(input.getValue(), input.getKey())); } }; } @@ -3074,7 +3351,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 +3386,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 +3398,14 @@ public static NavigableMap unmodifiableNavigableMap( } } - @NullableDecl - private static Entry unmodifiableOrNull(@NullableDecl Entry entry) { + private static + @Nullable Entry unmodifiableOrNull(@Nullable Entry entry) { return (entry == null) ? null : Maps.unmodifiableEntry(entry); } @GwtIncompatible // NavigableMap - static class UnmodifiableNavigableMap extends ForwardingSortedMap - implements NavigableMap, Serializable { + static class UnmodifiableNavigableMap + extends ForwardingSortedMap implements NavigableMap, Serializable { private final NavigableMap delegate; UnmodifiableNavigableMap(NavigableMap delegate) { @@ -3147,66 +3424,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,34 +3509,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) { + @ParametricNullness K fromKey, + boolean fromInclusive, + @ParametricNullness K toKey, + boolean toInclusive) { return Maps.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) { + public NavigableMap headMap(@ParametricNullness K toKey, boolean inclusive) { return Maps.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) { + public NavigableMap tailMap(@ParametricNullness K fromKey, boolean inclusive) { return Maps.unmodifiableNavigableMap(delegate.tailMap(fromKey, inclusive)); } } @@ -3314,8 +3594,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); } @@ -3324,14 +3605,16 @@ public static NavigableMap synchronizedNavigableMap( * 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 +3622,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 +3634,7 @@ Set createKeySet() { return new KeySet<>(this); } - @NullableDecl private transient Collection values; + @LazyInit private transient @Nullable Collection values; @Override public Collection values() { @@ -3364,7 +3647,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 +3680,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 +3693,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 +3706,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 +3716,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 +3737,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 +3756,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 +3765,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 +3790,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 +3825,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 +3844,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 +3864,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 +3909,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 +3950,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,7 +4001,7 @@ public Iterator iterator() { } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { try { return super.remove(o); } catch (UnsupportedOperationException e) { @@ -3725,7 +4020,7 @@ public boolean removeAll(Collection c) { try { return super.removeAll(checkNotNull(c)); } catch (UnsupportedOperationException e) { - Set toRemove = Sets.newHashSet(); + Set toRemove = newHashSet(); for (Entry entry : map().entrySet()) { if (c.contains(entry.getValue())) { toRemove.add(entry.getKey()); @@ -3740,7 +4035,7 @@ public boolean retainAll(Collection c) { try { return super.retainAll(checkNotNull(c)); } catch (UnsupportedOperationException e) { - Set toRetain = Sets.newHashSet(); + Set toRetain = newHashSet(); for (Entry entry : map().entrySet()) { if (c.contains(entry.getValue())) { toRetain.add(entry.getKey()); @@ -3761,7 +4056,7 @@ public boolean isEmpty() { } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { return map().containsValue(o); } @@ -3771,7 +4066,8 @@ public void clear() { } } - abstract static class EntrySet extends Sets.ImprovedAbstractSet> { + abstract static class EntrySet + extends Sets.ImprovedAbstractSet> { abstract Map map(); @Override @@ -3785,7 +4081,7 @@ 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(); @@ -3801,8 +4097,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 +4125,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 +4142,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 +4152,7 @@ protected final Map delegate() { return forward(); } - @NullableDecl private transient Comparator comparator; + @LazyInit private transient @Nullable Comparator comparator; @SuppressWarnings("unchecked") @Override @@ -3865,77 +4169,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 +4250,7 @@ public NavigableMap descendingMap() { return forward(); } - @NullableDecl private transient Set> entrySet; + @LazyInit private transient @Nullable Set> entrySet; @Override public Set> entrySet() { @@ -3975,7 +4281,7 @@ public Set keySet() { return navigableKeySet(); } - @NullableDecl private transient NavigableSet navigableKeySet; + @LazyInit private transient @Nullable NavigableSet navigableKeySet; @Override public NavigableSet navigableKeySet() { @@ -3990,32 +4296,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 +4346,7 @@ static ImmutableMap indexMap(Collection list) { for (E e : list) { builder.put(e, i++); } - return builder.build(); + return builder.buildOrThrow(); } /** @@ -4056,10 +4365,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..d365ffb718a9 100644 --- a/android/guava/src/com/google/common/collect/MinMaxPriorityQueue.java +++ b/android/guava/src/com/google/common/collect/MinMaxPriorityQueue.java @@ -21,9 +21,13 @@ 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; @@ -41,7 +45,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 @@ -96,7 +100,6 @@ * @author Torbjorn Gannholm * @since 8.0 */ -@Beta @GwtCompatible public final class MinMaxPriorityQueue extends AbstractQueue { @@ -105,7 +108,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(); } /** @@ -121,14 +124,21 @@ public static > MinMaxPriorityQueue create( * 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 +149,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 +164,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 @@ -207,7 +217,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 +234,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 +302,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 +339,7 @@ private int getMaxElementIndex() { * empty. */ @CanIgnoreReturnValue - public E pollFirst() { + public @Nullable E pollFirst() { return poll(); } @@ -343,7 +357,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 +366,7 @@ public E peekFirst() { * empty. */ @CanIgnoreReturnValue - public E pollLast() { + public @Nullable E pollLast() { return isEmpty() ? null : removeAndGet(getMaxElementIndex()); } @@ -373,7 +387,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 +406,7 @@ public E peekLast() { */ @VisibleForTesting @CanIgnoreReturnValue - MoveDesc removeAt(int index) { + @Nullable MoveDesc removeAt(int index) { checkPositionIndex(index, size); modCount++; size--; @@ -416,18 +430,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. @@ -497,14 +511,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 { + 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 +535,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 +555,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 +603,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 +639,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 +663,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; } } } @@ -751,9 +773,9 @@ private class QueueIterator implements Iterator { 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 +813,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 +828,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 +911,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 +948,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 +959,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; } } @@ -950,6 +974,6 @@ private int calculateNewCapacity() { /** 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..905bf14e8ec7 --- /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 +@SuppressWarnings("Java7ApiChecker") +@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. + */ + 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..a02c6ea6f6a2 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 @@ -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 @@ -240,7 +240,7 @@ boolean remove( * @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..8f8da008042c 100644 --- a/android/guava/src/com/google/common/collect/MultimapBuilder.java +++ b/android/guava/src/com/google/common/collect/MultimapBuilder.java @@ -34,19 +34,17 @@ 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. - * - *

    This can be used to easily configure multimap data structure implementations not provided - * explicitly in {@code com.google.common.collect}, for example: + * 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: * *

    {@code
    - * ListMultimap treeListMultimap =
    - *     MultimapBuilder.treeKeys().arrayListValues().build();
    - * SetMultimap hashEnumMultimap =
    - *     MultimapBuilder.hashKeys().enumSetValues(MyEnum.class).build();
    + * 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 @@ -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() { + public 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,13 @@ public static Supplier> instance() { } @Override - public List get() { + 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 +222,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 +236,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 +246,7 @@ private static final class TreeSetSupplier implements Supplier>, @Override public SortedSet get() { - return new TreeSet(comparator); + return new TreeSet<>(comparator); } } @@ -269,16 +271,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,11 +290,11 @@ 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(), new ArrayListSupplier(expectedValuesPerKey)); @@ -301,10 +303,10 @@ public ListMultimap build() { } /** Uses a {@link LinkedList} to store value collections. */ - public ListMultimapBuilder linkedListValues() { - return new ListMultimapBuilder() { + public ListMultimapBuilder linkedListValues() { + return new ListMultimapBuilder() { @Override - public ListMultimap build() { + public ListMultimap build() { return Multimaps.newListMultimap( MultimapBuilderWithKeys.this.createMap(), LinkedListSupplier.instance()); } @@ -312,7 +314,7 @@ public ListMultimap build() { } /** Uses a hash-based {@code Set} to store value collections. */ - public SetMultimapBuilder hashSetValues() { + public SetMultimapBuilder hashSetValues() { return hashSetValues(DEFAULT_EXPECTED_VALUES_PER_KEY); } @@ -322,11 +324,11 @@ 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(), new HashSetSupplier(expectedValuesPerKey)); @@ -335,7 +337,7 @@ public SetMultimap build() { } /** 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,11 +347,11 @@ 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(), new LinkedHashSetSupplier(expectedValuesPerKey)); @@ -369,7 +371,8 @@ 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 @@ -381,8 +384,7 @@ public SortedSetMultimap build() { } /** 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 @@ -416,7 +418,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 +429,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 +438,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 +449,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 +458,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 +469,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..26c9fc92d7b6 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 @@ -68,6 +73,104 @@ 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: + * + *

    {@code
    +   * 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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: + * + *

    {@code
    +   * 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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}. @@ -104,12 +207,13 @@ private Multimaps() {} * key * @throws IllegalArgumentException if {@code map} is not empty */ - public static Multimap newMultimap( + public static Multimap newMultimap( Map> map, final Supplier> factory) { return new CustomMultimap<>(map, factory); } - private static class CustomMultimap extends AbstractMapBasedMultimap { + private static 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,8 +270,11 @@ 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 */ + /** + * @serialData the factory and the backing map + */ @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); @@ -174,15 +282,17 @@ private void writeObject(ObjectOutputStream stream) throws IOException { } @GwtIncompatible // java.io.ObjectInputStream + @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 + @J2ktIncompatible private static final long serialVersionUID = 0; } @@ -216,12 +326,14 @@ 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, final Supplier> factory) { return new CustomListMultimap<>(map, factory); } - private static class CustomListMultimap extends AbstractListMultimap { + private static class CustomListMultimap + extends AbstractListMultimap { transient Supplier> factory; CustomListMultimap(Map> map, Supplier> factory) { @@ -244,8 +356,11 @@ protected List createCollection() { return factory.get(); } - /** @serialData the factory and the backing map */ + /** + * @serialData the factory and the backing map + */ @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); @@ -253,15 +368,17 @@ private void writeObject(ObjectOutputStream stream) throws IOException { } @GwtIncompatible // java.io.ObjectInputStream + @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 + @J2ktIncompatible private static final long serialVersionUID = 0; } @@ -294,12 +411,14 @@ 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, final Supplier> factory) { return new CustomSetMultimap<>(map, factory); } - private static class CustomSetMultimap extends AbstractSetMultimap { + private static class CustomSetMultimap + extends AbstractSetMultimap { transient Supplier> factory; CustomSetMultimap(Map> map, Supplier> factory) { @@ -323,7 +442,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 +454,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,8 +464,11 @@ Collection wrapCollection(K key, Collection collection) { } } - /** @serialData the factory and the backing map */ + /** + * @serialData the factory and the backing map + */ @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); @@ -353,15 +476,17 @@ private void writeObject(ObjectOutputStream stream) throws IOException { } @GwtIncompatible // java.io.ObjectInputStream + @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 + @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, final Supplier> factory) { return new CustomSortedSetMultimap<>(map, factory); } - private static class CustomSortedSetMultimap extends AbstractSortedSetMultimap { + private static 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,12 +553,15 @@ protected SortedSet createCollection() { } @Override - public Comparator valueComparator() { + public @Nullable Comparator valueComparator() { return valueComparator; } - /** @serialData the factory and the backing map */ + /** + * @serialData the factory and the backing map + */ @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); @@ -438,16 +569,18 @@ private void writeObject(ObjectOutputStream stream) throws IOException { } @GwtIncompatible // java.io.ObjectInputStream + @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 + @J2ktIncompatible private static final long serialVersionUID = 0; } @@ -463,8 +596,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()); @@ -504,7 +637,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 +654,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,19 +668,22 @@ 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) { this.delegate = checkNotNull(delegate); @@ -568,13 +707,7 @@ public Map> asMap() { map = Collections.unmodifiableMap( Maps.transformValues( - delegate.asMap(), - new Function, Collection>() { - @Override - public Collection apply(Collection collection) { - return unmodifiableValueCollection(collection); - } - })); + delegate.asMap(), collection -> unmodifiableValueCollection(collection))); } return result; } @@ -589,7 +722,7 @@ public Collection> entries() { } @Override - public Collection get(K key) { + public Collection get(@ParametricNullness K key) { return unmodifiableValueCollection(delegate.get(key)); } @@ -612,12 +745,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 +760,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(); } @@ -653,8 +786,9 @@ public Collection values() { private static final long serialVersionUID = 0; } - private static class UnmodifiableListMultimap extends UnmodifiableMultimap - implements ListMultimap { + private static class UnmodifiableListMultimap< + K extends @Nullable Object, V extends @Nullable Object> + extends UnmodifiableMultimap implements ListMultimap { UnmodifiableListMultimap(ListMultimap delegate) { super(delegate); } @@ -665,25 +799,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; } - 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 +829,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 +843,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; } - private static class UnmodifiableSortedSetMultimap extends UnmodifiableSetMultimap - implements SortedSetMultimap { + private static class UnmodifiableSortedSetMultimap< + K extends @Nullable Object, V extends @Nullable Object> + extends UnmodifiableSetMultimap implements SortedSetMultimap { UnmodifiableSortedSetMultimap(SortedSetMultimap delegate) { super(delegate); } @@ -732,22 +868,22 @@ 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(); } @@ -764,7 +900,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 +917,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 +931,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 +951,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 +968,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 +984,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 +1001,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 +1015,9 @@ public static ListMultimap unmodifiableListMultimap(ListMultimap ListMultimap unmodifiableListMultimap( ImmutableListMultimap delegate) { @@ -883,7 +1032,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 +1052,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 +1066,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 +1079,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 +1092,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 +1105,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 +1125,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 class MapMultimap + extends AbstractMultimap implements SetMultimap, Serializable { final Map map; MapMultimap(Map map) { @@ -994,22 +1147,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 final K key) { return new Sets.ImprovedAbstractSet() { @Override public Iterator iterator() { @@ -1022,12 +1175,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 +1205,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 +1220,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; } @@ -1170,8 +1328,10 @@ 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, final Function function) { checkNotNull(function); EntryTransformer transformer = Maps.asEntryTransformer(function); return transformEntries(fromMultimap, transformer); @@ -1217,8 +1377,10 @@ 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, final Function function) { checkNotNull(function); EntryTransformer transformer = Maps.asEntryTransformer(function); return transformEntries(fromMultimap, transformer); @@ -1275,8 +1437,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); } @@ -1328,12 +1492,16 @@ 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; @@ -1344,7 +1512,7 @@ private static class TransformedEntriesMultimap extends AbstractMulti this.transformer = checkNotNull(transformer); } - Collection transform(K key, Collection values) { + Collection transform(@ParametricNullness K key, Collection values) { Function function = Maps.asValueToValueFunction(transformer, key); if (values instanceof List) { return Lists.transform((List) values, function); @@ -1355,14 +1523,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(), (key, value) -> transform(key, value)); } @Override @@ -1371,7 +1532,7 @@ public void clear() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return fromMultimap.containsKey(key); } @@ -1387,7 +1548,7 @@ Iterator> entryIterator() { } @Override - public Collection get(final K key) { + public Collection get(@ParametricNullness final K key) { return transform(key, fromMultimap.get(key)); } @@ -1407,12 +1568,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 +1584,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(); } @@ -1450,7 +1611,8 @@ Collection createValues() { } } - 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 +1621,23 @@ private static final class TransformedEntriesListMultimap } @Override - List transform(K key, Collection values) { + List transform(@ParametricNullness K key, Collection values) { return Lists.transform((List) values, Maps.asValueToValueFunction(transformer, key)); } @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(); } } @@ -1568,7 +1730,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) { @@ -1583,6 +1746,7 @@ Iterator> entryIterator() { Multiset.Entry transform(final Map.Entry> backingEntry) { return new Multisets.AbstractEntry() { @Override + @ParametricNullness public K getElement() { return backingEntry.getKey(); } @@ -1607,7 +1771,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object element) { + public boolean contains(@Nullable Object element) { return multimap.containsKey(element); } @@ -1617,13 +1781,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 +1829,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 +1839,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 +1848,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 +1863,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,7 +1881,7 @@ protected Set>> createEntrySet() { return new EntrySet(); } - void removeValuesForKey(Object key) { + void removeValuesForKey(@Nullable Object key) { multimap.keySet().remove(key); } @@ -1728,22 +1894,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(), key -> multimap.get(key)); } @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 +1911,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 +1931,7 @@ public boolean isEmpty() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return multimap.containsKey(key); } @@ -1808,7 +1968,7 @@ public void clear() { * * @since 11.0 */ - public static Multimap filterKeys( + public static Multimap filterKeys( Multimap unfiltered, final Predicate keyPredicate) { if (unfiltered instanceof SetMultimap) { return filterKeys((SetMultimap) unfiltered, keyPredicate); @@ -1853,8 +2013,9 @@ public static Multimap filterKeys( * * @since 14.0 */ - public static SetMultimap filterKeys( - SetMultimap unfiltered, final Predicate keyPredicate) { + public static + SetMultimap filterKeys( + SetMultimap unfiltered, final Predicate keyPredicate) { if (unfiltered instanceof FilteredKeySetMultimap) { FilteredKeySetMultimap prev = (FilteredKeySetMultimap) unfiltered; return new FilteredKeySetMultimap<>( @@ -1894,8 +2055,9 @@ public static SetMultimap filterKeys( * * @since 14.0 */ - public static ListMultimap filterKeys( - ListMultimap unfiltered, final Predicate keyPredicate) { + public static + ListMultimap filterKeys( + ListMultimap unfiltered, final Predicate keyPredicate) { if (unfiltered instanceof FilteredKeyListMultimap) { FilteredKeyListMultimap prev = (FilteredKeyListMultimap) unfiltered; return new FilteredKeyListMultimap<>( @@ -1932,8 +2094,9 @@ public static ListMultimap filterKeys( * * @since 11.0 */ - public static Multimap filterValues( - Multimap unfiltered, final Predicate valuePredicate) { + public static + Multimap filterValues( + Multimap unfiltered, final Predicate valuePredicate) { return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); } @@ -1964,8 +2127,9 @@ public static Multimap filterValues( * * @since 14.0 */ - public static SetMultimap filterValues( - SetMultimap unfiltered, final Predicate valuePredicate) { + public static + SetMultimap filterValues( + SetMultimap unfiltered, final Predicate valuePredicate) { return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); } @@ -1994,8 +2158,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 +2195,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,8 +2210,9 @@ 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) { + private static + Multimap filterFiltered( + FilteredMultimap multimap, Predicate> entryPredicate) { Predicate> predicate = Predicates.>and(multimap.entryPredicate(), entryPredicate); return new FilteredEntryMultimap<>(multimap.unfiltered(), predicate); @@ -2057,14 +2224,15 @@ 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) { + 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..79255a9f9b74 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(); /** @@ -294,7 +306,7 @@ interface Entry { */ @Override // TODO(kevinb): check this wrt TreeMultiset? - boolean equals(Object o); + boolean equals(@Nullable Object o); /** * {@inheritDoc} @@ -328,7 +340,7 @@ 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 @@ -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..21704a232f22 100644 --- a/android/guava/src/com/google/common/collect/Multisets.java +++ b/android/guava/src/com/google/common/collect/Multisets.java @@ -20,8 +20,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.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.base.Predicate; @@ -30,6 +33,8 @@ 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; @@ -38,13 +43,17 @@ import java.util.Iterator; import java.util.NoSuchElementException; 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 +65,33 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +102,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 +118,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,7 +141,7 @@ protected Multiset delegate() { return (Multiset) delegate; } - @NullableDecl transient Set elementSet; + @LazyInit transient @Nullable Set elementSet; Set createElementSet() { return Collections.unmodifiableSet(delegate.elementSet()); @@ -112,7 +153,7 @@ public Set elementSet() { return (es == null) ? elementSet = createElementSet() : es; } - @NullableDecl transient Set> entrySet; + @LazyInit transient @Nullable Set> entrySet; @SuppressWarnings("unchecked") @Override @@ -131,12 +172,12 @@ public Iterator 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 +187,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,12 +212,12 @@ 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(); } @@ -194,10 +235,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 +249,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,7 +276,7 @@ public final int getCount() { return count; } - public ImmutableEntry nextInBucket() { + public @Nullable ImmutableEntry nextInBucket() { return null; } @@ -265,19 +308,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; @@ -319,7 +362,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 +373,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 +414,14 @@ static int inferDistinctElements(Iterable elements) { * * @since 14.0 */ - @Beta - public static Multiset union( + public static Multiset union( final Multiset multiset1, final 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 +431,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 @@ -410,11 +452,11 @@ Iterator> entryIterator() { // 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 +485,16 @@ protected Entry computeNext() { * * @since 2.0 */ - public static Multiset intersection( + public static Multiset intersection( final Multiset multiset1, final 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 @@ -471,11 +513,11 @@ Iterator> entryIterator() { // 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,8 +541,7 @@ protected Entry computeNext() { * * @since 14.0 */ - @Beta - public static Multiset sum( + public static Multiset sum( final Multiset multiset1, final Multiset multiset2) { checkNotNull(multiset1); checkNotNull(multiset2); @@ -508,7 +549,7 @@ public static Multiset sum( // 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 +564,7 @@ public int size() { } @Override - public int count(Object element) { + public int count(@Nullable Object element) { return multiset1.count(element) + multiset2.count(element); } @@ -543,7 +584,7 @@ Iterator> entryIterator() { final 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,8 +617,7 @@ protected Entry computeNext() { * * @since 14.0 */ - @Beta - public static Multiset difference( + public static Multiset difference( final Multiset multiset1, final Multiset multiset2) { checkNotNull(multiset1); checkNotNull(multiset2); @@ -585,9 +625,9 @@ public static Multiset difference( // 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 @@ -600,7 +640,7 @@ Iterator elementIterator() { final 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(); @@ -618,7 +658,7 @@ Iterator> entryIterator() { final 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 +721,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); @@ -789,13 +829,13 @@ 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() @@ -829,7 +869,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 +895,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 +909,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 +929,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 +960,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 +977,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 +990,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 +1010,7 @@ public void clear() { } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { return multiset().contains(o); } @@ -982,7 +1028,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 +1038,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 +1055,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 +1079,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 +1107,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public E next() { if (!hasNext()) { throw new NoSuchElementException(); @@ -1075,7 +1118,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 +1131,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 +1151,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 +1178,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..e50e4c27c345 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( + final 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()); + 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..c298f4a8e038 100644 --- a/android/guava/src/com/google/common/collect/NaturalOrdering.java +++ b/android/guava/src/com/google/common/collect/NaturalOrdering.java @@ -19,45 +19,50 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +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 { +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; } 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..82bcc2dbeaef --- /dev/null +++ b/android/guava/src/com/google/common/collect/NullnessCasts.java @@ -0,0 +1,66 @@ +/* + * 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}. + * (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}.) + */ + @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..b13a55213633 100644 --- a/android/guava/src/com/google/common/collect/NullsFirstOrdering.java +++ b/android/guava/src/com/google/common/collect/NullsFirstOrdering.java @@ -18,11 +18,13 @@ import com.google.common.annotations.GwtCompatible; 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 { +final class NullsFirstOrdering extends Ordering<@Nullable T> + implements Serializable { final Ordering ordering; NullsFirstOrdering(Ordering ordering) { @@ -30,7 +32,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 +46,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; } diff --git a/android/guava/src/com/google/common/collect/NullsLastOrdering.java b/android/guava/src/com/google/common/collect/NullsLastOrdering.java index 5dd8950c9f8a..3d8c1e9e0ca0 100644 --- a/android/guava/src/com/google/common/collect/NullsLastOrdering.java +++ b/android/guava/src/com/google/common/collect/NullsLastOrdering.java @@ -18,11 +18,13 @@ import com.google.common.annotations.GwtCompatible; 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 { +final class NullsLastOrdering extends Ordering<@Nullable T> + implements Serializable { final Ordering ordering; NullsLastOrdering(Ordering ordering) { @@ -30,7 +32,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 +46,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; } diff --git a/android/guava/src/com/google/common/collect/ObjectArrays.java b/android/guava/src/com/google/common/collect/ObjectArrays.java index e09fc69a3b85..3505037bf971 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. @@ -33,6 +35,7 @@ * @since 2.0 */ @GwtCompatible(emulated = true) +@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..250fcb77d415 100644 --- a/android/guava/src/com/google/common/collect/ObjectCountHashMap.java +++ b/android/guava/src/com/google/common/collect/ObjectCountHashMap.java @@ -28,21 +28,22 @@ 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 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 { +@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]; @@ -201,7 +209,7 @@ Entry getEntry(int index) { } class MapEntry extends AbstractEntry { - @NullableDecl final K key; + @ParametricNullness final K key; int lastKnownIndex; @@ -212,6 +220,7 @@ class MapEntry extends AbstractEntry { } @Override + @ParametricNullness public K getElement() { return key; } @@ -271,10 +280,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); @@ -316,7 +325,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,7 +386,7 @@ 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) { @@ -390,21 +399,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 diff --git a/android/guava/src/com/google/common/collect/ObjectCountLinkedHashMap.java b/android/guava/src/com/google/common/collect/ObjectCountLinkedHashMap.java index 5a5a4766a40d..1e8b092b9feb 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 { +@NullMarked +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..a3a268850aab 100644 --- a/android/guava/src/com/google/common/collect/Ordering.java +++ b/android/guava/src/com/google/common/collect/Ordering.java @@ -18,11 +18,18 @@ 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.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 +45,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 @@ -121,12 +129,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 +152,7 @@ * @since 2.0 */ @GwtCompatible -public abstract class Ordering implements Comparator { +public abstract class Ordering implements Comparator { // Natural order /** @@ -154,10 +162,12 @@ 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,7 +180,9 @@ 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 @@ -178,7 +190,7 @@ public static Ordering natural() { * 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 +201,12 @@ public static Ordering from(Comparator comparator) { * * @deprecated no need to use this */ + @InlineMe( + replacement = "checkNotNull(ordering)", + staticImports = "com.google.common.base.Preconditions.checkNotNull") @GwtCompatible(serializable = true) @Deprecated - public static Ordering from(Ordering ordering) { + public static Ordering from(Ordering ordering) { return checkNotNull(ordering); } @@ -217,7 +232,7 @@ public static Ordering from(Ordering ordering) { // TODO(kevinb): provide replacement @GwtCompatible(serializable = true) public static Ordering explicit(List valuesInOrder) { - return new ExplicitOrdering(valuesInOrder); + return new ExplicitOrdering<>(valuesInOrder); } /** @@ -271,14 +286,13 @@ 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,7 +302,7 @@ 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() { @@ -311,16 +325,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; } + @J2ktIncompatible // MapMaker private static class ArbitraryOrderingHolder { - static final Ordering ARBITRARY_ORDERING = new ArbitraryOrdering(); + 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 +359,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 +413,27 @@ 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 +441,14 @@ 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); } @@ -441,11 +462,11 @@ public Ordering nullsLast() { * .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); } @@ -462,13 +483,16 @@ 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,10 +504,12 @@ 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}). @@ -491,8 +517,9 @@ public Ordering compound(Comparator secondaryCompara * @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,7 +533,7 @@ 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 */ @@ -527,17 +554,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 +571,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 +587,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 +617,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 +626,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 +636,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 +653,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 +662,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 +678,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 +708,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 +717,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 +727,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 +747,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 +765,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 +783,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 @@ -761,16 +796,16 @@ public List leastOf(Iterator iterator, int k) { 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 +821,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 +832,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 +843,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 +852,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 +874,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 Lists.newArrayList(asList(array)); } /** @@ -859,7 +894,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 +903,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 +927,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 +954,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,7 +972,6 @@ 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 { final Object value; 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..141bff4c4d70 100644 --- a/android/guava/src/com/google/common/collect/Platform.java +++ b/android/guava/src/com/google/common/collect/Platform.java @@ -17,10 +17,11 @@ 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. @@ -30,7 +31,8 @@ @GwtCompatible(emulated = true) 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..7d050f33f5cb 100644 --- a/android/guava/src/com/google/common/collect/Queues.java +++ b/android/guava/src/com/google/common/collect/Queues.java @@ -14,11 +14,14 @@ package com.google.common.collect; -import com.google.common.annotations.Beta; +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 +35,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 @@ -50,9 +54,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 +68,7 @@ public static ArrayBlockingQueue newArrayBlockingQueue(int capacity) { * @since 12.0 */ public static ArrayDeque newArrayDeque() { - return new ArrayDeque(); + return new ArrayDeque<>(); } /** @@ -74,9 +79,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 +89,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 +118,10 @@ public static ConcurrentLinkedQueue newConcurrentLinkedQueue( * * @since 12.0 */ + @J2ktIncompatible @GwtIncompatible // LinkedBlockingDeque public static LinkedBlockingDeque newLinkedBlockingDeque() { - return new LinkedBlockingDeque(); + return new LinkedBlockingDeque<>(); } /** @@ -122,9 +130,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 +143,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 +157,10 @@ public static LinkedBlockingDeque newLinkedBlockingDeque(Iterable LinkedBlockingQueue newLinkedBlockingQueue() { - return new LinkedBlockingQueue(); + return new LinkedBlockingQueue<>(); } /** @@ -157,9 +168,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 +182,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 +201,14 @@ public static LinkedBlockingQueue newLinkedBlockingQueue(Iterable PriorityBlockingQueue newPriorityBlockingQueue() { - return new PriorityBlockingQueue(); + return new PriorityBlockingQueue<>(); } /** @@ -201,15 +217,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 +239,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 +253,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 +270,34 @@ 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 + @SuppressWarnings("Java7ApiChecker") + @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 { + // TODO(b/126049426): Consider using saturateToNanos(timeout) instead. + return drain(q, buffer, numElements, timeout.toNanos(), NANOSECONDS); } /** @@ -264,8 +312,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 +336,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 +347,30 @@ 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 + @SuppressWarnings("Java7ApiChecker") + @IgnoreJRERequirement // Users will use this only if they're already using Duration + public static int drainUninterruptibly( + BlockingQueue q, Collection buffer, int numElements, Duration timeout) { + // TODO(b/126049426): Consider using saturateToNanos(timeout) instead. + return drainUninterruptibly(q, buffer, numElements, timeout.toNanos(), 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 +384,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 +407,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 @@ -385,7 +457,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); } @@ -418,7 +491,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..f690e73045c4 100644 --- a/android/guava/src/com/google/common/collect/Range.java +++ b/android/guava/src/com/google/common/collect/Range.java @@ -16,18 +16,20 @@ 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.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 +106,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 +119,17 @@ * @since 10.0 */ @GwtCompatible -@SuppressWarnings("rawtypes") +@SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 +@Immutable(containerOf = "C") 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("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); } /** @@ -255,9 +231,8 @@ public static > Range upTo(C endpoint, BoundType boun return lessThan(endpoint); case CLOSED: return atMost(endpoint); - default: - throw new AssertionError(); } + throw new AssertionError(); } /** @@ -290,9 +265,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 +303,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 +314,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 +413,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 +431,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 +530,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 +647,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 +678,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() { @@ -714,7 +703,7 @@ 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(); + static final Ordering INSTANCE = new RangeLexOrdering(); @Override public int compare(Range left, Range right) { diff --git a/android/guava/src/com/google/common/collect/RangeGwtSerializationDependencies.java b/android/guava/src/com/google/common/collect/RangeGwtSerializationDependencies.java index 222c1285fb4b..b0b67d4071f7 100644 --- a/android/guava/src/com/google/common/collect/RangeGwtSerializationDependencies.java +++ b/android/guava/src/com/google/common/collect/RangeGwtSerializationDependencies.java @@ -28,5 +28,6 @@ * *

    TODO(cpovirk): Consider applying this subclass approach to our other types. */ +@SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 @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..76f2098b9d52 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 @@ -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..8732c4eef3d8 100644 --- a/android/guava/src/com/google/common/collect/RegularContiguousSet.java +++ b/android/guava/src/com/google/common/collect/RegularContiguousSet.java @@ -18,12 +18,16 @@ 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. @@ -31,7 +35,7 @@ * @author Gregory Kick */ @GwtCompatible(emulated = true) -@SuppressWarnings("unchecked") // allow ungenerified Comparable types +@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 // serialization + @GwtIncompatible // serialization + 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) { @@ -214,6 +240,7 @@ public int hashCode() { } @GwtIncompatible // serialization + @J2ktIncompatible private static final class SerializedForm implements Serializable { final Range range; final DiscreteDomain domain; @@ -224,14 +251,21 @@ private SerializedForm(Range range, DiscreteDomain domain) { } private Object readResolve() { - return new RegularContiguousSet(range, domain); + return new RegularContiguousSet<>(range, domain); } } @GwtIncompatible // serialization + @J2ktIncompatible @Override Object writeReplace() { - return new SerializedForm(range, domain); + return new SerializedForm<>(range, domain); + } + + @GwtIncompatible // serialization + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); } 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..557359ad753d 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 @@ -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 // serialization + @GwtIncompatible // serialization + 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..253af71b056e 100644 --- a/android/guava/src/com/google/common/collect/RegularImmutableBiMap.java +++ b/android/guava/src/com/google/common/collect/RegularImmutableBiMap.java @@ -17,8 +17,10 @@ package com.google.common.collect; 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. @@ -30,8 +32,8 @@ 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 +49,23 @@ 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); + RegularImmutableMap.createHashTableOrThrow(alternatingKeysAndValues, size, tableSize, 0); Object valueHashTable = - RegularImmutableMap.createHashTable(alternatingKeysAndValues, size, tableSize, 1); + RegularImmutableMap.createHashTableOrThrow(alternatingKeysAndValues, size, tableSize, 1); this.inverse = 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 // serialization + @GwtIncompatible // serialization + 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..2f167282a143 100644 --- a/android/guava/src/com/google/common/collect/RegularImmutableList.java +++ b/android/guava/src/com/google/common/collect/RegularImmutableList.java @@ -17,9 +17,14 @@ 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. @@ -31,10 +36,11 @@ 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 // serialization + @GwtIncompatible // serialization + 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..c0801713d996 100644 --- a/android/guava/src/com/google/common/collect/RegularImmutableMap.java +++ b/android/guava/src/com/google/common/collect/RegularImmutableMap.java @@ -19,13 +19,16 @@ 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}. @@ -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]; } } @@ -268,11 +379,15 @@ ImmutableSet> createEntrySet() { static 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 // serialization + @GwtIncompatible // serialization + 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 // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } @SuppressWarnings("unchecked") @@ -423,7 +576,17 @@ boolean isPartialView() { return false; } + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + 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) + @J2ktIncompatible // serialization 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..17eeb52f0003 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. @@ -38,7 +39,7 @@ class RegularImmutableMultiset extends ImmutableMultiset { 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 + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + 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++; @@ -128,8 +139,9 @@ Object readResolve() { private static final long serialVersionUID = 0; } - @GwtIncompatible @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization 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..b62931a2e302 100644 --- a/android/guava/src/com/google/common/collect/RegularImmutableSet.java +++ b/android/guava/src/com/google/common/collect/RegularImmutableSet.java @@ -16,9 +16,13 @@ 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. @@ -28,29 +32,32 @@ @GwtCompatible(serializable = true, emulated = true) @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 // serialization + @GwtIncompatible // serialization + 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..f7d2804f08bb 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 @@ -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 // serialization + @GwtIncompatible // serialization + 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..820bd100e09b 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. @@ -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,6 +70,15 @@ public boolean contains(@NullableDecl Object object) { boolean isPartialView() { return false; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } abstract V getValue(int iterationIndex); @@ -93,12 +104,21 @@ public V get(int index) { boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + 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 // serialization + @GwtIncompatible // serialization + 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..23c609209f49 100644 --- a/android/guava/src/com/google/common/collect/ReverseNaturalOrdering.java +++ b/android/guava/src/com/google/common/collect/ReverseNaturalOrdering.java @@ -24,64 +24,64 @@ /** 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 { +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); } diff --git a/android/guava/src/com/google/common/collect/ReverseOrdering.java b/android/guava/src/com/google/common/collect/ReverseOrdering.java index 9f65e5976034..310b9663a279 100644 --- a/android/guava/src/com/google/common/collect/ReverseOrdering.java +++ b/android/guava/src/com/google/common/collect/ReverseOrdering.java @@ -21,11 +21,12 @@ import com.google.common.annotations.GwtCompatible; 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 { +final class ReverseOrdering extends Ordering + implements Serializable { final Ordering forwardOrder; ReverseOrdering(Ordering forwardOrder) { @@ -33,7 +34,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 +47,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 +68,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 +94,7 @@ public int hashCode() { } @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/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..4c8435fe3059 100644 --- a/android/guava/src/com/google/common/collect/Sets.java +++ b/android/guava/src/com/google/common/collect/Sets.java @@ -19,15 +19,20 @@ 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.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,14 +53,16 @@ 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 @@ -70,7 +77,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,7 +100,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) { @@ -110,7 +116,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) { @@ -134,6 +139,19 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +174,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 +197,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 +216,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,7 +242,8 @@ 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) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static HashSet newHashSet(Iterator elements) { HashSet set = newHashSet(); Iterators.addAll(set, elements); return set; @@ -237,8 +261,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 +307,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,18 +325,21 @@ 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(); Iterables.addAll(set, elements); @@ -327,8 +358,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 +372,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 +398,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 +409,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 +425,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,7 +451,7 @@ public static TreeSet newTreeSet(Comparator comparator) { * * @since 8.0 */ - public static Set newIdentityHashSet() { + public static Set newIdentityHashSet() { return Collections.newSetFromMap(Maps.newIdentityHashMap()); } @@ -418,9 +464,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 +477,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 +503,8 @@ public static CopyOnWriteArraySet newCopyOnWriteArraySet(Iterable> EnumSet complementOf(Collection collection) { if (collection instanceof EnumSet) { return EnumSet.complementOf((EnumSet) collection); @@ -474,6 +525,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 +535,8 @@ public static > EnumSet complementOf( : makeComplementByHand(collection, type); } + @J2ktIncompatible + @GwtIncompatible private static > EnumSet makeComplementByHand( Collection collection, Class type) { EnumSet result = EnumSet.allOf(type); @@ -518,8 +573,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 +589,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 +600,18 @@ 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 upperBoundSize = upperBoundSize(); + if (upperBoundSize == 0) { + return ImmutableSet.of(); + } + ImmutableSet.Builder<@NonNull E> builder = + ImmutableSet.builderWithExpectedSize(upperBoundSize); + for (E element : this) { + builder.add(checkNotNull(element)); + } + return builder.build(); } /** @@ -571,7 +638,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 +652,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 +666,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 +680,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 +694,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 +707,7 @@ public final boolean retainAll(Collection elementsToKeep) { */ @Deprecated @Override + @DoNotCall("Always throws UnsupportedOperationException") public final void clear() { throw new UnsupportedOperationException(); } @@ -646,6 +719,18 @@ public final void clear() { */ @Override public abstract UnmodifiableIterator iterator(); + + /** + * Returns the upper bound on the size of this set view. + * + *

    This method is used to presize the underlying collection when converting to an {@link + * ImmutableSet}. + */ + abstract int upperBoundSize(); + + static int upperBoundSize(Set set) { + return set instanceof SetView ? ((SetView) set).upperBoundSize() : set.size(); + } } /** @@ -658,7 +743,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( + final Set set1, final Set set2) { checkNotNull(set1, "set1"); checkNotNull(set2, "set2"); @@ -686,7 +772,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 +788,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 +800,8 @@ public > S copyInto(S set) { } @Override - public ImmutableSet immutableCopy() { - return new ImmutableSet.Builder().addAll(set1).addAll(set2).build(); + int upperBoundSize() { + return upperBoundSize(set1) + upperBoundSize(set2); } }; } @@ -747,7 +833,8 @@ public ImmutableSet immutableCopy() { * *

    This is unfortunate, but should come up only very rarely. */ - public static SetView intersection(final Set set1, final Set set2) { + public static SetView intersection( + final Set set1, final Set set2) { checkNotNull(set1, "set1"); checkNotNull(set2, "set2"); @@ -758,7 +845,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 +874,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 +882,11 @@ public boolean contains(Object object) { public boolean containsAll(Collection collection) { return set1.containsAll(collection) && set2.containsAll(collection); } + + @Override + int upperBoundSize() { + return min(upperBoundSize(set1), upperBoundSize(set2)); + } }; } @@ -808,7 +900,8 @@ 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( + final Set set1, final Set set2) { checkNotNull(set1, "set1"); checkNotNull(set2, "set2"); @@ -819,7 +912,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 +941,14 @@ public boolean isEmpty() { } @Override - public boolean contains(Object element) { + public boolean contains(@Nullable Object element) { return set1.contains(element) && !set2.contains(element); } + + @Override + int upperBoundSize() { + return upperBoundSize(set1); + } }; } @@ -865,7 +963,7 @@ public boolean contains(Object element) { * * @since 3.0 */ - public static SetView symmetricDifference( + public static SetView symmetricDifference( final Set set1, final Set set2) { checkNotNull(set1, "set1"); checkNotNull(set2, "set2"); @@ -877,7 +975,7 @@ public UnmodifiableIterator iterator() { final 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 +1015,14 @@ public boolean isEmpty() { } @Override - public boolean contains(Object element) { + public boolean contains(@Nullable Object element) { return set1.contains(element) ^ set2.contains(element); } + + @Override + int upperBoundSize() { + return upperBoundSize(set1) + upperBoundSize(set2); + } }; } @@ -945,12 +1048,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 +1062,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 +1093,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 +1131,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 +1161,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,7 +1210,7 @@ public E last() { } @GwtIncompatible // NavigableSet - private static class FilteredNavigableSet extends FilteredSortedSet + private static class FilteredNavigableSet extends FilteredSortedSet implements NavigableSet { FilteredNavigableSet(NavigableSet unfiltered, Predicate predicate) { super(unfiltered, predicate); @@ -1113,34 +1221,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 +1261,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); } } @@ -1289,7 +1399,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> @@ -1323,6 +1433,15 @@ public List get(int index) { boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } }; return new CartesianSet(axes, new CartesianList(listAxes)); } @@ -1338,7 +1457,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 +1476,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 @@ -1457,7 +1580,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; } @@ -1487,13 +1610,13 @@ public Iterator> iterator() { return new AbstractIndexedListIterator>(size()) { @Override protected Set get(final int setBits) { - return new SubSet(inputSet, 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 +1625,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,7 +1673,6 @@ 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); checkNonnegative(size, "size"); @@ -1562,7 +1684,7 @@ public static Set> combinations(Set set, final int size) { } 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 +1698,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 +1708,7 @@ protected Set computeNext() { if (bitToFlip == index.size()) { return endOfData(); } + /* * The current set in sorted order looks like * {firstSetBit, firstSetBit + 1, ..., bitToFlip - 1, ...} @@ -1606,7 +1729,7 @@ protected Set computeNext() { final 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 +1740,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 +1784,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 +1809,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 +1839,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,18 +1887,21 @@ 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)); } @@ -1820,12 +1949,16 @@ public NavigableSet tailSet(E fromElement, boolean inclusive) { *

    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 +1981,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 +1991,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 +2004,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 +2045,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 +2089,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 +2111,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 +2143,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..09894261d31e 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. @@ -29,44 +33,32 @@ @GwtCompatible(serializable = true, emulated = true) @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 // serialization + @GwtIncompatible // serialization + 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..5e4975ce2895 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; /** @@ -76,7 +78,9 @@ ImmutableCollection createValues() { } @Override - SerializedForm createSerializedForm() { + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + 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..8042b36703fb 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 { +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..530b663be6cc 100644 --- a/android/guava/src/com/google/common/collect/SortedMultisets.java +++ b/android/guava/src/com/google/common/collect/SortedMultisets.java @@ -28,7 +28,7 @@ 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. @@ -40,7 +40,9 @@ 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..f6ab2c94c45a 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; @@ -61,12 +65,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,14 +83,14 @@ 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 @@ -123,12 +131,15 @@ V getValue(int index) { } @Override - SerializedForm createSerializedForm() { + @J2ktIncompatible // serialization + @GwtIncompatible // serialization + 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..3ae5ba24ee0c 100644 --- a/android/guava/src/com/google/common/collect/StandardRowSortedTable.java +++ b/android/guava/src/com/google/common/collect/StandardRowSortedTable.java @@ -26,6 +26,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 @@ -102,7 +103,7 @@ SortedSet createKeySet() { } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return sortedBackingMap().comparator(); } diff --git a/android/guava/src/com/google/common/collect/StandardTable.java b/android/guava/src/com/google/common/collect/StandardTable.java index efed27a3451b..4cddcaa586f2 100644 --- a/android/guava/src/com/google/common/collect/StandardTable.java +++ b/android/guava/src/com/google/common/collect/StandardTable.java @@ -21,8 +21,13 @@ 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.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; @@ -32,6 +37,7 @@ 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 +46,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 @@ -74,12 +80,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 +98,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 +144,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 +153,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 +169,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 +185,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; @@ -232,7 +240,7 @@ Iterator> cellIterator() { private class CellIterator implements Iterator> { final Iterator>> rowIterator = backingMap.entrySet().iterator(); - @NullableDecl Entry> rowEntry; + @Nullable Entry> rowEntry; Iterator> columnIterator = Iterators.emptyModifiableIterator(); @Override @@ -246,14 +254,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 +304,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 +348,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 +360,7 @@ public V remove(Object key) { @Override public void clear() { - Map backingRowMap = backingRowMap(); + updateBackingRowMapField(); if (backingRowMap != null) { backingRowMap.clear(); } @@ -336,17 +369,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(); + final Iterator> iterator = backingRowMap.entrySet().iterator(); return new Iterator>() { @Override public boolean hasNext() { @@ -379,7 +412,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); } @@ -405,22 +438,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 +466,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()) { @@ -478,7 +511,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 +520,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()); @@ -505,7 +538,7 @@ private 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(); if (entry.getValue().containsKey(columnKey)) { @@ -523,7 +556,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(); @@ -545,12 +593,12 @@ private class KeySet extends Maps.KeySet { } @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; } @@ -572,7 +620,7 @@ private class Values extends Maps.Values { } @Override - public boolean remove(Object obj) { + public boolean remove(@Nullable Object obj) { return obj != null && removeFromColumnIf(Maps.valuePredicateOnEntries(equalTo(obj))); } @@ -593,7 +641,7 @@ public Set rowKeySet() { return rowMap().keySet(); } - @NullableDecl private transient Set columnKeySet; + @LazyInit private transient @Nullable Set columnKeySet; /** * {@inheritDoc} @@ -622,7 +670,7 @@ public int size() { } @Override - public boolean remove(Object obj) { + public boolean remove(@Nullable Object obj) { if (obj == null) { return false; } @@ -677,7 +725,7 @@ public boolean retainAll(Collection c) { } @Override - public boolean contains(Object obj) { + public boolean contains(@Nullable Object obj) { return containsColumn(obj); } } @@ -692,10 +740,10 @@ private class ColumnKeyIterator extends AbstractIterator { // 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 +771,7 @@ public Collection values() { return super.values(); } - @NullableDecl private transient Map> rowMap; + @LazyInit private transient @Nullable Map> rowMap; @Override public Map> rowMap() { @@ -738,19 +786,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,7 +809,7 @@ protected Set>> createEntrySet() { } @WeakOuter - class EntrySet extends TableSet>> { + private final class EntrySet extends TableSet>> { @Override public Iterator>> iterator() { return Maps.asMapEntryIterator( @@ -779,7 +828,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 +839,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 +851,7 @@ public boolean remove(Object obj) { } } - @NullableDecl private transient ColumnMap columnMap; + @LazyInit private transient @Nullable ColumnMap columnMap; @Override public Map> columnMap() { @@ -816,17 +865,18 @@ private class ColumnMap extends ViewCachingAbstractMap> { // 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,7 +896,7 @@ Collection> createValues() { } @WeakOuter - class ColumnMapEntrySet extends TableSet>> { + private final class ColumnMapEntrySet extends TableSet>> { @Override public Iterator>> iterator() { return Maps.asMapEntryIterator( @@ -865,23 +915,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 +944,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 +957,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; } @@ -922,7 +973,7 @@ private class ColumnMapValues extends Maps.Values> { } @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()); 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..3c242e94b9b1 --- /dev/null +++ b/android/guava/src/com/google/common/collect/Streams.java @@ -0,0 +1,1000 @@ +/* + * 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 +@SuppressWarnings("Java7ApiChecker") +/* + * 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: + * + *

    {@code
    +   * 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: + * + *

    {@code
    +   * Streams.forEachPair(
    +   *   Stream.of("foo1", "foo2", "foo3"),
    +   *   Stream.of("bar1", "bar2"),
    +   *   (arg1, arg2) -> System.out.println(arg1 + ":" + arg2)
    +   * }
    + * + *

    will print: + * + *

    {@code
    +   * 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 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, + * + *

    {@code
    +   * 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); + } + 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, + * + *

    {@code
    +   * 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. + */ + @SuppressWarnings("AndroidJdkLibsChecker") // b/229998664 + 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); + } + class Splitr extends MapWithIndexSpliterator + implements IntConsumer, Spliterator { + 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, + * + *

    {@code
    +   * 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. + */ + @SuppressWarnings("AndroidJdkLibsChecker") // b/229998664 + 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); + } + class Splitr extends MapWithIndexSpliterator + implements LongConsumer, Spliterator { + 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, + * + *

    {@code
    +   * 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. + */ + @SuppressWarnings("AndroidJdkLibsChecker") // b/229998664 + 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); + } + class Splitr extends MapWithIndexSpliterator + implements DoubleConsumer, Spliterator { + 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) { + 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..b1cd1c317657 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,7 +55,17 @@ * @author Mike Bostock * @author Jared Levy */ +@J2ktIncompatible @GwtCompatible(emulated = true) +/* + * 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() {} @@ -62,7 +73,7 @@ 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; } @@ -86,6 +97,7 @@ public String toString() { // following writeObject() handles the SynchronizedObject members. @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { synchronized (mutex) { stream.defaultWriteObject(); @@ -93,17 +105,19 @@ private void writeObject(ObjectOutputStream stream) throws IOException { } @GwtIncompatible // not needed in emulated source + @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 +149,7 @@ public void clear() { } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { synchronized (mutex) { return delegate().contains(o); } @@ -161,7 +175,7 @@ public Iterator iterator() { } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { synchronized (mutex) { return delegate().remove(o); } @@ -189,14 +203,15 @@ 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); } @@ -206,13 +221,14 @@ public T[] toArray(T[] a) { } @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 +238,7 @@ Set delegate() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o == this) { return true; } @@ -241,12 +257,14 @@ public int hashCode() { 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 +274,7 @@ SortedSet delegate() { } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { synchronized (mutex) { return delegate().comparator(); } @@ -300,14 +318,15 @@ public E last() { 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) { + static class SynchronizedList extends SynchronizedCollection + implements List { + SynchronizedList(List delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -338,14 +357,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 +402,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; } @@ -402,28 +421,29 @@ public int hashCode() { 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; } - 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 +453,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 +498,7 @@ public Set elementSet() { } @Override - public Set> entrySet() { + public Set> entrySet() { synchronized (mutex) { if (entrySet == null) { entrySet = typePreservingSet(delegate().entrySet(), mutex); @@ -488,7 +508,7 @@ public Set> entrySet() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o == this) { return true; } @@ -507,20 +527,21 @@ public int hashCode() { 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; + 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 +549,7 @@ Multimap delegate() { return (Multimap) super.delegate(); } - SynchronizedMultimap(Multimap delegate, @NullableDecl Object mutex) { + SynchronizedMultimap(Multimap delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -547,42 +568,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 +617,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 +665,7 @@ public Collection values() { } @Override - public Collection> entries() { + public Collection> entries() { synchronized (mutex) { if (entries == null) { entries = typePreservingCollection(delegate().entries(), mutex); @@ -674,7 +695,7 @@ public Multiset keys() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o == this) { return true; } @@ -693,17 +714,18 @@ public int hashCode() { 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 } @@ -736,19 +758,19 @@ public List replaceValues(K key, Iterable values) { 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; + static class SynchronizedSetMultimap + 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 +787,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 +801,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); @@ -791,17 +813,19 @@ public Set> entries() { 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 +842,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,7 +856,7 @@ public SortedSet replaceValues(K key, Iterable values) { } @Override - public Comparator valueComparator() { + public @Nullable Comparator valueComparator() { synchronized (mutex) { return delegate().valueComparator(); } @@ -841,8 +865,8 @@ public Comparator valueComparator() { 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 +879,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 +888,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(final Map.Entry> entry) { return new ForwardingMapEntry>() { @Override - protected Entry> delegate() { + protected Map.Entry> delegate() { return entry; } @@ -894,21 +920,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 +955,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 +965,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); } @@ -956,16 +989,18 @@ public boolean retainAll(Collection c) { } @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; + 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 +1018,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 +1042,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 +1066,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 +1080,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 +1104,7 @@ public Collection values() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o == this) { return true; } @@ -1088,14 +1123,15 @@ public int hashCode() { 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 +1141,7 @@ SortedMap delegate() { } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { synchronized (mutex) { return delegate().comparator(); } @@ -1149,21 +1185,21 @@ public SortedMap tailMap(K fromKey) { 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, Serializable { + 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 +1220,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); } @@ -1203,16 +1239,17 @@ public BiMap inverse() { 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 +1257,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,7 +1277,7 @@ public Collection> values() { } @Override - public boolean containsValue(Object o) { + public boolean containsValue(@Nullable Object o) { // values() and its contains() method are both synchronized. return values().contains(o); } @@ -1248,8 +1285,9 @@ public boolean containsValue(Object o) { 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); } @@ -1269,9 +1307,9 @@ Collection transform(Collection from) { @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 +1319,7 @@ NavigableSet delegate() { } @Override - public E ceiling(E e) { + public @Nullable E ceiling(E e) { synchronized (mutex) { return delegate().ceiling(e); } @@ -1292,7 +1330,7 @@ public Iterator descendingIterator() { return delegate().descendingIterator(); // manually synchronized } - @NullableDecl transient NavigableSet descendingSet; + transient @Nullable NavigableSet descendingSet; @Override public NavigableSet descendingSet() { @@ -1307,7 +1345,7 @@ public NavigableSet descendingSet() { } @Override - public E floor(E e) { + public @Nullable E floor(E e) { synchronized (mutex) { return delegate().floor(e); } @@ -1326,28 +1364,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(); } @@ -1383,33 +1421,35 @@ public SortedSet tailSet(E fromElement) { } @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 +1459,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 +1484,7 @@ public NavigableSet descendingKeySet() { } } - @NullableDecl transient NavigableMap descendingMap; + transient @Nullable NavigableMap descendingMap; @Override public NavigableMap descendingMap() { @@ -1457,21 +1497,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 +1530,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 +1569,7 @@ public Set keySet() { return navigableKeySet(); } - @NullableDecl transient NavigableSet navigableKeySet; + transient @Nullable NavigableSet navigableKeySet; @Override public NavigableSet navigableKeySet() { @@ -1542,14 +1582,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); } @@ -1584,8 +1624,9 @@ public SortedMap tailMap(K fromKey) { } @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 +1634,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); } @@ -1643,13 +1685,14 @@ public V setValue(V value) { 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 { + static class SynchronizedQueue extends SynchronizedCollection + implements Queue { - SynchronizedQueue(Queue delegate, @NullableDecl Object mutex) { + SynchronizedQueue(Queue delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -1673,14 +1716,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(); } @@ -1696,13 +1739,14 @@ public E remove() { 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 +1798,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 +1826,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); } @@ -1833,14 +1877,16 @@ public Iterator descendingIterator() { 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 +1897,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 +1953,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 +1970,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); } @@ -1973,7 +2022,7 @@ public Collection values() { public Map> rowMap() { synchronized (mutex) { return map( - Maps.transformValues( + transformValues( delegate().rowMap(), new com.google.common.base.Function, Map>() { @Override @@ -1989,7 +2038,7 @@ public Map apply(Map t) { public Map> columnMap() { synchronized (mutex) { return map( - Maps.transformValues( + transformValues( delegate().columnMap(), new com.google.common.base.Function, Map>() { @Override @@ -2009,7 +2058,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..054ec519d282 100644 --- a/android/guava/src/com/google/common/collect/Table.java +++ b/android/guava/src/com/google/common/collect/Table.java @@ -24,7 +24,7 @@ import java.util.Collection; import java.util.Map; 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..1db160bff5a7 --- /dev/null +++ b/android/guava/src/com/google/common/collect/TableCollectors.java @@ -0,0 +1,221 @@ +/* + * 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 +@SuppressWarnings("Java7ApiChecker") +@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); + } + } + + 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..60dd8b3e9150 100644 --- a/android/guava/src/com/google/common/collect/Tables.java +++ b/android/guava/src/com/google/common/collect/Tables.java @@ -18,9 +18,10 @@ 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 com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Supplier; @@ -33,13 +34,15 @@ 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 +52,67 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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,33 +122,44 @@ 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; } @@ -92,12 +167,14 @@ public V getValue() { 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; } @@ -133,13 +210,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 class TransposeTable< + C extends @Nullable Object, R extends @Nullable Object, V extends @Nullable Object> + extends AbstractTable { final Table original; TransposeTable(Table original) { @@ -152,7 +232,7 @@ public void clear() { } @Override - public Map column(R columnKey) { + public Map column(@ParametricNullness R columnKey) { return original.row(columnKey); } @@ -167,32 +247,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 +285,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 +314,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 +364,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 +393,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 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 +418,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); } @@ -446,13 +540,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 +571,7 @@ public void clear() { } @Override - public Map column(@NullableDecl C columnKey) { + public Map column(@ParametricNullness C columnKey) { return Collections.unmodifiableMap(super.column(columnKey)); } @@ -492,7 +587,10 @@ public Map> columnMap() { } @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 +600,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)); } @@ -542,9 +640,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,8 +651,9 @@ 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) { super(delegate); @@ -580,7 +679,8 @@ public SortedSet rowKeySet() { } @SuppressWarnings("unchecked") - private static Function, Map> unmodifiableWrapper() { + private static + Function, Map> unmodifiableWrapper() { return (Function) UNMODIFIABLE_WRAPPER; } @@ -621,11 +721,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..0509021ad5dc 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,15 +110,16 @@ 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; @@ -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,13 @@ private void swap(int i, int j) { buffer[j] = tmp; } + 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 +274,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..232b3e6a43ab 100644 --- a/android/guava/src/com/google/common/collect/TreeBasedTable.java +++ b/android/guava/src/com/google/common/collect/TreeBasedTable.java @@ -18,10 +18,13 @@ 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.base.Supplier; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.Comparator; import java.util.Iterator; @@ -31,7 +34,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,7 +62,7 @@ * 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 @@ -69,7 +72,7 @@ public class TreeBasedTable extends StandardRowSortedTable { private final Comparator columnComparator; - private static class Factory implements Supplier>, Serializable { + private static class Factory implements Supplier>, Serializable { final Comparator comparator; Factory(Comparator comparator) { @@ -77,7 +80,7 @@ private static class Factory implements Supplier>, Serializa } @Override - public TreeMap get() { + public Map get() { return new TreeMap<>(comparator); } @@ -92,6 +95,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()); } @@ -133,9 +137,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()); } /** @@ -169,14 +180,14 @@ public SortedMap row(R rowKey) { } private class TreeRow extends Row implements SortedMap { - @NullableDecl final C lowerBound; - @NullableDecl final C upperBound; + 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 +212,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 +238,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 +281,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,12 +290,12 @@ 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); } @@ -312,25 +316,18 @@ public SortedMap> 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; diff --git a/android/guava/src/com/google/common/collect/TreeMultimap.java b/android/guava/src/com/google/common/collect/TreeMultimap.java index 85531e3e3b33..a7b729b90da8 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 { +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); } @@ -200,6 +206,7 @@ public NavigableMap> asMap() { * distinct key: the key, number of values for that key, and key values */ @GwtIncompatible // java.io.ObjectOutputStream + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(keyComparator()); @@ -208,15 +215,17 @@ private void writeObject(ObjectOutputStream stream) throws IOException { } @GwtIncompatible // java.io.ObjectInputStream + @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 + @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..ecef8a46eaea 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 { +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. @@ -361,6 +371,7 @@ public void clear() { private Entry wrapEntry(final 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() { + public @Nullable T get() { return value; } - public void checkAndSet(@NullableDecl T expected, T newValue) { + public 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,31 @@ 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); + 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); + 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 +704,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 +756,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 +805,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 +868,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 +885,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 +895,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 +907,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 +925,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 +936,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 +983,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 +1003,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 +1014,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 +1030,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,6 +1051,7 @@ 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 */ + @J2ktIncompatible @GwtIncompatible // java.io.ObjectOutputStream private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); @@ -1001,23 +1059,25 @@ private void writeObject(ObjectOutputStream stream) throws IOException { Serialization.writeMultiset(this, stream); } + @J2ktIncompatible @GwtIncompatible // java.io.ObjectInputStream 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 + @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..a5be86f00e70 100644 --- a/android/guava/src/com/google/common/collect/TreeRangeMap.java +++ b/android/guava/src/com/google/common/collect/TreeRangeMap.java @@ -21,15 +21,17 @@ 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.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -37,7 +39,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,20 +50,47 @@ * @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 TreeRangeMap(NavigableMap, RangeMapEntry> entriesByLowerBound) { + this.entriesByLowerBound = entriesByLowerBound; + } + private static final class RangeMapEntry extends AbstractMapEntry, V> { private final Range range; @@ -100,15 +129,13 @@ Cut getUpperBound() { } @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 +182,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 +192,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 +207,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 +289,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 +327,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,22 +374,22 @@ 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; } @@ -377,18 +404,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 +467,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,7 +513,7 @@ public Map, V> asDescendingMapOfRanges() { @Override Iterator, V>> entryIterator() { if (subRange.isEmpty()) { - return Iterators.emptyIterator(); + return emptyIterator(); } final Iterator> backingItr = entriesByLowerBound @@ -499,13 +524,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 +540,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 +561,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 +599,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; } @@ -607,7 +633,7 @@ 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; } @@ -650,7 +676,7 @@ public boolean isEmpty() { Iterator, V>> entryIterator() { if (subRange.isEmpty()) { - return Iterators.emptyIterator(); + return emptyIterator(); } Cut cutToStart = MoreObjects.firstNonNull( @@ -660,14 +686,14 @@ Iterator, V>> entryIterator() { 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(); @@ -693,7 +719,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..11f585c35ef3 100644 --- a/android/guava/src/com/google/common/collect/TreeRangeSet.java +++ b/android/guava/src/com/google/common/collect/TreeRangeSet.java @@ -16,11 +16,13 @@ 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.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 +32,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 +40,6 @@ * @author Louis Wasserman * @since 14.0 */ -@Beta @GwtIncompatible // uses NavigableMap public class TreeRangeSet> extends AbstractRangeSet implements Serializable { @@ -47,7 +48,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 +77,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 +113,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 +152,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 +164,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 +187,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 +232,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 +272,7 @@ private void replaceRangeWithSameLowerBound(Range range) { } } - @NullableDecl private transient RangeSet complement; + @LazyInit private transient @Nullable RangeSet complement; @Override public RangeSet complement() { @@ -301,7 +304,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(); } @@ -332,12 +335,12 @@ public Comparator> comparator() { } @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 +365,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 +385,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 +393,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 +411,20 @@ Iterator, Range>> descendingEntryIterator() { } else { candidates = rangesByLowerBound.descendingMap().values(); } - final PeekingIterator> backingItr = Iterators.peekingIterator(candidates.iterator()); + PeekingIterator> backingItr = Iterators.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(); } }; @@ -462,7 +465,7 @@ private static final class ComplementRangesByLowerBound> 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 +474,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); } } @@ -521,22 +524,21 @@ Iterator, Range>> entryIterator() { } else { positiveRanges = positiveRangesByUpperBound.values(); } - final PeekingIterator> positiveItr = - Iterators.peekingIterator(positiveRanges.iterator()); - final Cut firstComplementRangeLowerBound; + PeekingIterator> positiveItr = Iterators.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(); @@ -550,7 +552,7 @@ protected Entry, Range> computeNext() { negativeRange = Range.create(nextComplementRangeLowerBound, Cut.aboveAll()); nextComplementRangeLowerBound = Cut.aboveAll(); } - return Maps.immutableEntry(negativeRange.lowerBound, negativeRange); + return immutableEntry(negativeRange.lowerBound, negativeRange); } }; } @@ -572,7 +574,7 @@ Iterator, Range>> descendingEntryIterator() { boolean inclusive = complementLowerBoundWindow.hasUpperBound() && complementLowerBoundWindow.upperBoundType() == BoundType.CLOSED; - final PeekingIterator> positiveItr = + PeekingIterator> positiveItr = Iterators.peekingIterator( positiveRangesByUpperBound .headMap(startingPoint, inclusive) @@ -587,17 +589,16 @@ Iterator, Range>> descendingEntryIterator() { : positiveRangesByLowerBound.higherKey(positiveItr.peek().upperBound); } else if (!complementLowerBoundWindow.contains(Cut.belowAll()) || positiveRangesByLowerBound.containsKey(Cut.belowAll())) { - return Iterators.emptyIterator(); + return emptyIterator(); } else { cut = positiveRangesByLowerBound.higherKey(Cut.belowAll()); } - final Cut firstComplementRangeUpperBound = - MoreObjects.firstNonNull(cut, Cut.aboveAll()); + Cut firstComplementRangeUpperBound = MoreObjects.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 +607,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); nextComplementRangeUpperBound = Cut.belowAll(); - return Maps.immutableEntry(Cut.belowAll(), negativeRange); + return immutableEntry(Cut.belowAll(), negativeRange); } return endOfData(); } @@ -624,8 +625,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 +643,7 @@ public Range get(Object key) { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return get(key) != null; } } @@ -698,14 +698,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); } } @@ -737,13 +737,12 @@ public Comparator> comparator() { } @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 +773,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 +792,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 +806,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 +815,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 +830,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 +840,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(); } @@ -880,8 +879,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..b483ca0b3319 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 @@ -47,8 +49,8 @@ * *

    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
      * // won't work
    @@ -74,6 +76,8 @@
     @Beta
     @GwtCompatible
     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.
    @@ -130,7 +134,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
    @@ -197,7 +201,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 +216,7 @@ protected T computeNext() {
         }
     
         private PostOrderNode expand(T t) {
    -      return new PostOrderNode(t, children(t).iterator());
    +      return new PostOrderNode<>(t, children(t).iterator());
         }
       }
     
    @@ -242,7 +246,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..ddf332487160 100644
    --- a/android/guava/src/com/google/common/collect/UnmodifiableSortedMultiset.java
    +++ b/android/guava/src/com/google/common/collect/UnmodifiableSortedMultiset.java
    @@ -16,11 +16,14 @@
     
     package com.google.common.collect;
     
    +import static com.google.common.collect.Sets.unmodifiableNavigableSet;
    +
     import com.google.common.annotations.GwtCompatible;
     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
    @@ -30,7 +33,7 @@
      * @author Louis Wasserman
      */
     @GwtCompatible(emulated = true)
    -final class UnmodifiableSortedMultiset extends UnmodifiableMultiset
    +final class UnmodifiableSortedMultiset extends UnmodifiableMultiset
         implements SortedMultiset {
       UnmodifiableSortedMultiset(SortedMultiset delegate) {
         super(delegate);
    @@ -48,7 +51,7 @@ public Comparator comparator() {
     
       @Override
       NavigableSet createElementSet() {
    -    return Sets.unmodifiableNavigableSet(delegate().elementSet());
    +    return unmodifiableNavigableSet(delegate().elementSet());
       }
     
       @Override
    @@ -56,13 +59,13 @@ public NavigableSet elementSet() {
         return (NavigableSet) super.elementSet();
       }
     
    -  @NullableDecl private transient UnmodifiableSortedMultiset descendingMultiset;
    +  @LazyInit private transient @Nullable UnmodifiableSortedMultiset descendingMultiset;
     
       @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,39 +73,42 @@ 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));
       }
     
    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..d7b1e6b2a9c9 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,7 @@ * @author David Beaumont * @since 15.0 */ -@Beta @GwtCompatible -@ElementTypesAreNonnullByDefault public abstract class ArrayBasedUnicodeEscaper extends UnicodeEscaper { // The replacement array (see ArrayBasedEscaperMap). private final char[][] replacements; @@ -130,10 +127,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 +160,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 +200,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..8a90efd8ae3c 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,7 @@ * @author Sven Mawson * @since 15.0 */ -@Beta @GwtCompatible -@ElementTypesAreNonnullByDefault public abstract class CharEscaper extends Escaper { /** Constructor for use by subclasses. */ protected CharEscaper() {} @@ -82,8 +79,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..3496d91366f2 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,9 +32,7 @@ * @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 @@ -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..16d9b6d1994c 100644 --- a/android/guava/src/com/google/common/escape/Escaper.java +++ b/android/guava/src/com/google/common/escape/Escaper.java @@ -56,7 +56,6 @@ */ @DoNotMock("Use Escapers.nullEscaper() or another methods from the *Escapers classes") @GwtCompatible -@ElementTypesAreNonnullByDefault public abstract class Escaper { // TODO(dbeaumont): evaluate custom implementations, considering package private constructor. /** Constructor for use by subclasses. */ @@ -85,13 +84,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..3480ee215e91 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; /** @@ -22,13 +24,13 @@ * @author Jesse Wilson */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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..725032964899 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,7 @@ * @author David Beaumont * @since 15.0 */ -@Beta @GwtCompatible -@ElementTypesAreNonnullByDefault 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 +76,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..c941a04a11a6 100644 --- a/android/guava/src/com/google/common/eventbus/Dispatcher.java +++ b/android/guava/src/com/google/common/eventbus/Dispatcher.java @@ -15,6 +15,7 @@ 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.Iterator; @@ -31,7 +32,6 @@ * * @author Colin Decker */ -@ElementTypesAreNonnullByDefault abstract class Dispatcher { /** @@ -97,7 +97,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 +134,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 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..0449efe2e113 100644 --- a/android/guava/src/com/google/common/eventbus/Subscribe.java +++ b/android/guava/src/com/google/common/eventbus/Subscribe.java @@ -35,5 +35,4 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -@ElementTypesAreNonnullByDefault 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..bd87333de9d8 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}. */ @@ -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..f6beaad511d7 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; @@ -53,17 +52,23 @@ public EventBus getEventBus() { return eventBus; } - /** @return The event object that caused the subscriber to throw. */ + /** + * @return The event object that caused the subscriber to throw. + */ public Object getEvent() { return event; } - /** @return The object context that the subscriber was called on. */ + /** + * @return The object context that the subscriber was called on. + */ public Object getSubscriber() { return subscriber; } - /** @return The subscribed method that threw the exception. */ + /** + * @return 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..94bf471053f5 100644 --- a/android/guava/src/com/google/common/eventbus/SubscriberRegistry.java +++ b/android/guava/src/com/google/common/eventbus/SubscriberRegistry.java @@ -16,12 +16,10 @@ 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; @@ -46,14 +44,13 @@ 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 +147,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,7 +167,24 @@ 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; } } @@ -220,15 +228,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 +238,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 { @@ -259,7 +257,7 @@ public int hashCode() { } @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..9ce3838947b8 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) { + @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 @@ -192,7 +181,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..2516b0239bb1 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 @@ -194,8 +196,7 @@ public boolean apply(E edge) { } @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 +209,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 +240,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 +278,42 @@ public String toString() { + edgeIncidentNodesMap(this); } + /** + * 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(final Network network) { - Function> edgeToIncidentNodesFn = - new Function>() { - @Override - public EndpointPair apply(E edge) { - return network.incidentNodes(edge); - } - }; - return Maps.asMap(network.edges(), edgeToIncidentNodesFn); + 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..fc637441c199 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; } @@ -138,13 +141,10 @@ public String toString() { } 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); + 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/BaseGraph.java b/android/guava/src/com/google/common/graph/BaseGraph.java index 1df5de7f174e..a451989e712b 100644 --- a/android/guava/src/com/google/common/graph/BaseGraph.java +++ b/android/guava/src/com/google/common/graph/BaseGraph.java @@ -70,44 +70,93 @@ interface BaseGraph extends SuccessorsFunction, PredecessorsFunction { // /** - * 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 `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 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 `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 `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 `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 */ 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..234c9bb3ee6c 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)); } - @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..88562a3bda3f 100644 --- a/android/guava/src/com/google/common/graph/ElementOrder.java +++ b/android/guava/src/com/google/common/graph/ElementOrder.java @@ -28,7 +28,7 @@ import com.google.errorprone.annotations.Immutable; import java.util.Comparator; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Used to represent the order of elements in a data structure that supports different options for @@ -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; } @@ -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..3bf575186525 100644 --- a/android/guava/src/com/google/common/graph/EndpointPair.java +++ b/android/guava/src/com/google/common/graph/EndpointPair.java @@ -24,7 +24,7 @@ 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 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,7 +133,7 @@ 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(), @@ -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; } @@ -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..b9ebd5cd1a0c 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 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..8d25db4ad646 100644 --- a/android/guava/src/com/google/common/graph/GraphBuilder.java +++ b/android/guava/src/com/google/common/graph/GraphBuilder.java @@ -22,19 +22,26 @@ 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
    @@ -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..25ef8ebadd54 100644 --- a/android/guava/src/com/google/common/graph/Graphs.java +++ b/android/guava/src/com/google/common/graph/Graphs.java @@ -18,21 +18,24 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.graph.GraphConstants.NODE_NOT_IN_GRAPH; +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.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.Map; +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 +45,7 @@ * @since 20.0 */ @Beta -public final class Graphs { +public final class Graphs extends GraphsBridgeMethods { private Graphs() {} @@ -67,7 +70,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 +96,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,7 +164,7 @@ && subgraphHasCycle(graph, visitedNodes, nextNode, node)) { * from B to A). */ private static boolean canTraverseWithoutReusingEdge( - Graph graph, Object nextNode, @NullableDecl Object previousNode) { + Graph graph, Object nextNode, @Nullable Object previousNode) { if (graph.isDirected() || !Objects.equal(previousNode, nextNode)) { return true; } @@ -145,10 +181,13 @@ private static boolean canTraverseWithoutReusingEdge( *

    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 33.1.0 (present with return type {@code Graph} since 20.0) */ // TODO(b/31438252): Consider potential optimizations for this algorithm. - public static Graph transitiveClosure(Graph graph) { - MutableGraph transitiveClosure = GraphBuilder.from(graph).allowsSelfLoops(true).build(); + public static ImmutableGraph transitiveClosure(Graph graph) { + ImmutableGraph.Builder transitiveClosure = + GraphBuilder.from(graph).allowsSelfLoops(true).immutable(); // 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. @@ -162,7 +201,7 @@ public static Graph transitiveClosure(Graph graph) { } 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(); + Set visitedNodes = new HashSet<>(); for (N node : graph.nodes()) { if (!visitedNodes.contains(node)) { Set reachableNodes = reachableNodes(graph, node); @@ -177,7 +216,7 @@ public static Graph transitiveClosure(Graph graph) { } } - return transitiveClosure; + return transitiveClosure.build(); } /** @@ -190,8 +229,9 @@ public static Graph transitiveClosure(Graph graph) { * not be updated after modifications to {@code graph}. * * @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 +253,7 @@ public static Graph transpose(Graph graph) { return ((TransposedGraph) graph).graph; } - return new TransposedGraph(graph); + return new TransposedGraph<>(graph); } /** @@ -286,12 +326,7 @@ public Set> incidentEdges(N node) { 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())); } }; } @@ -362,14 +397,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(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); } } @@ -433,12 +466,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 +533,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 +592,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..56e018ad6f52 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") diff --git a/android/guava/src/com/google/common/graph/ImmutableNetwork.java b/android/guava/src/com/google/common/graph/ImmutableNetwork.java index b35d7220817d..3589759c4620 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,31 +118,16 @@ 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); } /** diff --git a/android/guava/src/com/google/common/graph/ImmutableValueGraph.java b/android/guava/src/com/google/common/graph/ImmutableValueGraph.java index f2e2386340eb..25e23c22015c 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) diff --git a/android/guava/src/com/google/common/graph/IncidentEdgeSet.java b/android/guava/src/com/google/common/graph/IncidentEdgeSet.java index 0c318d9f6f2a..19e882e0e84b 100644 --- a/android/guava/src/com/google/common/graph/IncidentEdgeSet.java +++ b/android/guava/src/com/google/common/graph/IncidentEdgeSet.java @@ -18,7 +18,7 @@ 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 @@ -34,7 +34,7 @@ abstract class IncidentEdgeSet extends AbstractSet> { } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { throw new UnsupportedOperationException(); } @@ -50,7 +50,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object obj) { + public boolean contains(@Nullable Object obj) { if (!(obj instanceof EndpointPair)) { return false; } 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..f261cc09cbde --- /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; + + public static final 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..9f05b5c66509 100644 --- a/android/guava/src/com/google/common/graph/Network.java +++ b/android/guava/src/com/google/common/graph/Network.java @@ -19,7 +19,7 @@ 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 {@code - * MutableNetwork graph = NetworkBuilder.directed().build(); + * 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);
    + * ImmutableNetwork immutableGraph = ImmutableNetwork.copyOf(network);
      * }
    * *

    Instances of {@link ImmutableNetwork} do not implement {@link MutableNetwork} (obviously!) and @@ -159,69 +159,135 @@ public interface Network extends SuccessorsFunction, PredecessorsFuncti // /** - * Returns the nodes which have an incident edge in common with {@code node} in this network. + * Returns a live view of 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)}. * + *

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

    In an undirected network, this is equivalent to {@link #adjacentNodes(Object)}. * + *

    If {@code node} is removed from the network 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 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. + * Returns a live view of 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)}. * + *

    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 */ @Override Set successors(N node); /** - * Returns the edges whose {@link #incidentNodes(Object) incident nodes} in this network include - * {@code node}. + * Returns a live view of the edges whose {@link #incidentNodes(Object) incident nodes} in this + * network include {@code node}. * *

    This is equal to the union of {@link #inEdges(Object)} and {@link #outEdges(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 + * @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); @@ -269,21 +335,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 +384,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,30 +425,28 @@ 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 */ @@ -352,8 +457,8 @@ 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 @@ -385,7 +490,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..9af9acc74e3f 100644 --- a/android/guava/src/com/google/common/graph/NetworkBuilder.java +++ b/android/guava/src/com/google/common/graph/NetworkBuilder.java @@ -21,20 +21,26 @@ 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
    @@ -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/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..568f0f63274d 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) { + @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/Traverser.java b/android/guava/src/com/google/common/graph/Traverser.java index 0d8e6f97fa32..3c6076730fe2 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: @@ -176,7 +177,7 @@ Traversal newTraversal() { * @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."); } @@ -237,7 +238,7 @@ public final Iterable breadthFirst(N startNode) { * @since 24.1 */ public final Iterable breadthFirst(Iterable startNodes) { - final ImmutableSet validated = validate(startNodes); + ImmutableSet validated = validate(startNodes); return new Iterable() { @Override public Iterator iterator() { @@ -292,7 +293,7 @@ public final Iterable depthFirstPreOrder(N startNode) { * @since 24.1 */ public final Iterable depthFirstPreOrder(Iterable startNodes) { - final ImmutableSet validated = validate(startNodes); + ImmutableSet validated = validate(startNodes); return new Iterable() { @Override public Iterator iterator() { @@ -347,7 +348,7 @@ public final Iterable depthFirstPostOrder(N startNode) { * @since 24.1 */ public final Iterable depthFirstPostOrder(Iterable startNodes) { - final ImmutableSet validated = validate(startNodes); + ImmutableSet validated = validate(startNodes); return new Iterable() { @Override public Iterator iterator() { @@ -380,13 +381,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 +410,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 +435,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 +459,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 +473,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 +492,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..9d9bd8d6b90b 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 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: + * + *

    + * * @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..0d32004b2707 100644 --- a/android/guava/src/com/google/common/graph/ValueGraphBuilder.java +++ b/android/guava/src/com/google/common/graph/ValueGraphBuilder.java @@ -22,18 +22,25 @@ 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
    @@ -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..bd96e8aead3f 100644 --- a/android/guava/src/com/google/common/hash/AbstractByteHasher.java +++ b/android/guava/src/com/google/common/hash/AbstractByteHasher.java @@ -31,8 +31,6 @@ * * @author Colin Decker */ -@CanIgnoreReturnValue -@ElementTypesAreNonnullByDefault abstract class AbstractByteHasher extends AbstractHasher { private final ByteBuffer scratch = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); @@ -64,6 +62,7 @@ protected void update(ByteBuffer b) { } /** Updates the sink with the given number of bytes from the buffer. */ + @CanIgnoreReturnValue private Hasher update(int bytes) { try { update(scratch.array(), 0, bytes); @@ -74,12 +73,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 +88,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,30 +96,35 @@ 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) { scratch.putShort(s); return update(Shorts.BYTES); } @Override + @CanIgnoreReturnValue public Hasher putInt(int i) { scratch.putInt(i); return update(Ints.BYTES); } @Override + @CanIgnoreReturnValue public Hasher putLong(long l) { scratch.putLong(l); return update(Longs.BYTES); } @Override + @CanIgnoreReturnValue public Hasher putChar(char c) { scratch.putChar(c); return update(Chars.BYTES); 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..358150bdd707 100644 --- a/android/guava/src/com/google/common/hash/BloomFilter.java +++ b/android/guava/src/com/google/common/hash/BloomFilter.java @@ -16,6 +16,7 @@ import static com.google.common.base.Preconditions.checkArgument; 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.VisibleForTesting; @@ -23,18 +24,22 @@ import com.google.common.base.Predicate; import com.google.common.hash.BloomFilterStrategies.LockFreeBitArray; import com.google.common.math.DoubleMath; +import com.google.common.math.LongMath; 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.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,7 +69,6 @@ * @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. @@ -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) { @@ -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; } @@ -294,6 +305,76 @@ public int hashCode() { return Objects.hashCode(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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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); + } + /** * Creates a {@link BloomFilter} with the expected number of insertions and expected false * positive probability. @@ -364,9 +445,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 +515,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,13 +542,17 @@ 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() { return new SerialForm(this); } + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + private static class SerialForm implements Serializable { final long[] data; final int numHashFunctions; @@ -523,6 +606,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"); @@ -540,12 +624,16 @@ public void writeTo(OutputStream out) throws IOException { dataLength = din.readInt(); 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(LongMath.checkedMultiply(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 +645,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/ElementTypesAreNonnullByDefault.java b/android/guava/src/com/google/common/hash/ElementTypesAreNonnullByDefault.java deleted file mode 100755 index a2382b3514cc..000000000000 --- a/android/guava/src/com/google/common/hash/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.hash; - -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/hash/FarmHashFingerprint64.java b/android/guava/src/com/google/common/hash/FarmHashFingerprint64.java index 7d6a3981d79c..3785093f0924 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(); @@ -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") 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..f54232bc482f --- /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..d5e7482b7cba 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}. @@ -44,7 +44,6 @@ */ @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..5e643c334395 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() {} @@ -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,6 +122,10 @@ Object writeReplace() { return new SerializedForm(charset); } + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + private static class SerializedForm implements Serializable { private final String charsetCanonicalName; @@ -168,7 +172,7 @@ public String toString() { */ public static Funnel> sequentialFunnel( Funnel elementFunnel) { - return new SequentialFunnel(elementFunnel); + return new SequentialFunnel<>(elementFunnel); } private static class SequentialFunnel @@ -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); diff --git a/android/guava/src/com/google/common/hash/HashCode.java b/android/guava/src/com/google/common/hash/HashCode.java index fde2a86d6466..6d5d9adc5565 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; @@ -289,7 +288,7 @@ public long asLong() { @Override public long padToLong() { long retVal = (bytes[0] & 0xFF); - for (int i = 1; i < Math.min(bytes.length, 8); i++) { + for (int i = 1; i < min(bytes.length, 8); i++) { retVal |= (bytes[i] & 0xFFL) << (i * 8); } return retVal; @@ -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); diff --git a/android/guava/src/com/google/common/hash/HashFunction.java b/android/guava/src/com/google/common/hash/HashFunction.java index 2712dfce1e59..0da69abd4026 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,9 +115,7 @@ * @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} diff --git a/android/guava/src/com/google/common/hash/Hasher.java b/android/guava/src/com/google/common/hash/Hasher.java index b3f24fa282b9..218310449c2b 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 @@ -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..84396d227071 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; } @@ -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); 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/guava/src/com/google/common/hash/LongAddable.java b/android/guava/src/com/google/common/hash/IgnoreJRERequirement.java similarity index 55% rename from guava/src/com/google/common/hash/LongAddable.java rename to android/guava/src/com/google/common/hash/IgnoreJRERequirement.java index 5c6a7f0c7db8..8799c56dd850 100644 --- a/guava/src/com/google/common/hash/LongAddable.java +++ b/android/guava/src/com/google/common/hash/IgnoreJRERequirement.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 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,17 +14,16 @@ package com.google.common.hash; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; /** - * Abstract interface for objects that can concurrently add longs. + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. * - * @author Louis Wasserman + *

    Each package's copy of this annotation needs to be listed in our {@code pom.xml}. */ -@ElementTypesAreNonnullByDefault -interface LongAddable { - void increment(); - - void add(long x); - - long sum(); -} +@Target({METHOD, CONSTRUCTOR, TYPE}) +@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..24148966386c 100644 --- a/android/guava/src/com/google/common/hash/LittleEndianByteArray.java +++ b/android/guava/src/com/google/common/hash/LittleEndianByteArray.java @@ -14,8 +14,15 @@ 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 sun.misc.Unsafe; /** @@ -24,11 +31,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 +70,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 +108,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 +126,8 @@ private interface LittleEndianBytes { long getLongLittleEndian(byte[] array, int offset); void putLongLittleEndian(byte[] array, int offset, long value); + + boolean usesFastPath(); } /** @@ -124,7 +135,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 +166,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 +178,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 +219,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 +245,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 +266,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"); + 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; + 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..bd08428ff3a4 100644 --- a/android/guava/src/com/google/common/hash/LongAdder.java +++ b/android/guava/src/com/google/common/hash/LongAdder.java @@ -38,7 +38,6 @@ * @since 1.8 * @author Doug Lea */ -@ElementTypesAreNonnullByDefault final class LongAdder extends Striped64 implements Serializable, LongAddable { private static final long serialVersionUID = 7249069246863182397L; 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..3ce78e8614c6 100644 --- a/android/guava/src/com/google/common/hash/Murmur3_128HashFunction.java +++ b/android/guava/src/com/google/common/hash/Murmur3_128HashFunction.java @@ -31,7 +31,7 @@ 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; 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..4ed25af7291e 100644 --- a/android/guava/src/com/google/common/hash/Murmur3_32HashFunction.java +++ b/android/guava/src/com/google/common/hash/Murmur3_32HashFunction.java @@ -28,8 +28,8 @@ 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.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..ae180d781565 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,6 +32,7 @@ * @author Sebastian Kanthak * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible class AppendableWriter extends Writer { private final Appendable target; @@ -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..663638c22b92 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,7 +37,8 @@ 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 @@ -43,7 +46,7 @@ * href="http://tools.ietf.org/html/rfc4648">RFC 4648. For example, the expression: * *

    {@code
    - * BaseEncoding.base32().encode("foo".getBytes(Charsets.US_ASCII))
    + * BaseEncoding.base32().encode("foo".getBytes(US_ASCII))
      * }
    * *

    returns the string {@code "MZXW6==="}, and @@ -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+/", '='); @@ -417,7 +430,7 @@ 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 +440,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 +455,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 +487,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 +570,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 +583,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 +597,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 +634,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 +685,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 +699,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 +767,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 +776,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 +880,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 +904,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 +933,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 +948,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 +990,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); } @@ -985,13 +1047,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 +1080,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 +1099,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 +1159,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 +1204,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 +1235,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..d938fdf6bb4e 100644 --- a/android/guava/src/com/google/common/io/ByteSource.java +++ b/android/guava/src/com/google/common/io/ByteSource.java @@ -18,9 +18,10 @@ 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 +41,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 +74,7 @@ * @since 14.0 * @author Colin Decker */ +@J2ktIncompatible @GwtIncompatible public abstract class ByteSource { @@ -176,7 +179,6 @@ public boolean isEmpty() throws IOException { * * @since 19.0 */ - @Beta public Optional sizeIfKnown() { return Optional.absent(); } @@ -312,9 +314,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(); @@ -544,7 +546,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 +559,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 +571,9 @@ public String toString() { } } - private static class ByteArrayByteSource extends ByteSource { + private static class ByteArrayByteSource extends + ByteSource + { final byte[] bytes; final int offset; @@ -592,7 +596,7 @@ public InputStream openStream() { } @Override - public InputStream openBufferedStream() throws IOException { + public InputStream openBufferedStream() { return openStream(); } @@ -618,7 +622,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 +644,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..17e8b9c92638 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. @@ -49,6 +52,7 @@ * @author Colin Decker * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible public final class ByteStreams { @@ -94,6 +98,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 @@ -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,6 @@ 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 public static ByteArrayDataInput newDataInput(byte[] bytes) { return newDataInput(new ByteArrayInputStream(bytes)); } @@ -293,7 +312,6 @@ public static ByteArrayDataInput newDataInput(byte[] bytes) { * @throws IndexOutOfBoundsException if {@code start} is negative or greater than the length of * the array */ - @Beta public static ByteArrayDataInput newDataInput(byte[] bytes, int start) { checkPositionIndex(start, bytes.length); return newDataInput(new ByteArrayInputStream(bytes, start, bytes.length - start)); @@ -306,7 +324,6 @@ public static ByteArrayDataInput newDataInput(byte[] bytes, int start) { * * @since 17.0 */ - @Beta public static ByteArrayDataInput newDataInput(ByteArrayInputStream byteArrayInputStream) { return new ByteArrayDataInputStream(checkNotNull(byteArrayInputStream)); } @@ -319,7 +336,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 +345,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 +455,7 @@ public double readDouble() { } @Override - public String readLine() { + public @Nullable String readLine() { try { return input.readLine(); } catch (IOException e) { @@ -457,7 +474,6 @@ public String readUTF() { } /** Returns a new {@link ByteArrayDataOutput} instance with a default size. */ - @Beta public static ByteArrayDataOutput newDataOutput() { return newDataOutput(new ByteArrayOutputStream()); } @@ -468,7 +484,6 @@ public static ByteArrayDataOutput newDataOutput() { * * @throws IllegalArgumentException if {@code size} is negative */ - @Beta 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,7 +505,6 @@ public static ByteArrayDataOutput newDataOutput(int size) { * * @since 17.0 */ - @Beta public static ByteArrayDataOutput newDataOutput(ByteArrayOutputStream byteArrayOutputStream) { return new ByteArrayDataOutputStream(checkNotNull(byteArrayOutputStream)); } @@ -653,6 +667,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 @@ -666,7 +681,6 @@ public String toString() { * * @since 14.0 (since 1.0 as com.google.common.io.NullOutputStream) */ - @Beta public static OutputStream nullOutputStream() { return NULL_OUTPUT_STREAM; } @@ -679,7 +693,6 @@ public static OutputStream nullOutputStream() { * @return a length-limited {@link InputStream} * @since 14.0 (since 1.0 as com.google.common.io.LimitInputStream) */ - @Beta public static InputStream limit(InputStream in, long limit) { return new LimitedInputStream(in, limit); } @@ -698,7 +711,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 +740,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 +763,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 +779,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 +795,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 +812,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 +825,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 +837,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 +865,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 +877,10 @@ 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 + public static T readBytes( + InputStream input, ByteProcessor processor) throws IOException { checkNotNull(input); checkNotNull(processor); @@ -905,7 +916,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.) 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..a013d519a442 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,45 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +172,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..aba363c4b86e 100644 --- a/android/guava/src/com/google/common/io/CharSource.java +++ b/android/guava/src/com/google/common/io/CharSource.java @@ -15,9 +15,10 @@ 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; @@ -25,16 +26,21 @@ 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.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,55 @@ 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: + * + *

    {@code
    +   * 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 + @SuppressWarnings("Java7ApiChecker") + // 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)); + } + + @SuppressWarnings("Java7ApiChecker") + @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 +191,6 @@ public BufferedReader openBufferedStream() throws IOException { * * @since 19.0 */ - @Beta public Optional lengthIfKnown() { return Optional.absent(); } @@ -160,7 +214,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()) { @@ -260,8 +313,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()); @@ -315,9 +367,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 +383,34 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + /* + * 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 @@ -506,7 +586,7 @@ private Iterator linesIterator() { 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 +600,15 @@ protected String computeNext() { } @Override - public String readFirstLine() { + @SuppressWarnings("Java7ApiChecker") + // 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 +619,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())) { diff --git a/android/guava/src/com/google/common/io/CharStreams.java b/android/guava/src/com/google/common/io/CharStreams.java index c700691b1c11..5fcb7be50495 100644 --- a/android/guava/src/com/google/common/io/CharStreams.java +++ b/android/guava/src/com/google/common/io/CharStreams.java @@ -17,8 +17,8 @@ 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.Closeable; import java.io.EOFException; @@ -28,13 +28,11 @@ 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}. @@ -44,6 +42,7 @@ * @author Colin Decker * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible public final class CharStreams { @@ -193,7 +192,6 @@ private static StringBuilder toStringBuilder(Readable r) throws IOException { * @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); @@ -213,9 +211,10 @@ public static List readLines(Readable r) 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 readLines(Readable readable, LineProcessor processor) throws IOException { + @ParametricNullness + public static T readLines( + Readable readable, LineProcessor processor) throws IOException { checkNotNull(readable); checkNotNull(processor); @@ -235,7 +234,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 +255,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) { @@ -274,7 +271,6 @@ public static void skipFully(Reader reader, long n) throws IOException { * * @since 15.0 */ - @Beta public static Writer nullWriter() { return NullWriter.INSTANCE; } @@ -307,12 +303,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 +338,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..e9c580af36a6 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()); @@ -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..b6bf2cbef42c 100644 --- a/android/guava/src/com/google/common/io/Closer.java +++ b/android/guava/src/com/google/common/io/Closer.java @@ -15,28 +15,26 @@ 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: * @@ -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..69aabd8448a9 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,22 +62,22 @@ * @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 { @@ -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,8 +231,8 @@ 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. 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..f2557890778c 100644 --- a/android/guava/src/com/google/common/io/Files.java +++ b/android/guava/src/com/google/common/io/Files.java @@ -20,6 +20,7 @@ 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; @@ -32,6 +33,8 @@ 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; @@ -52,6 +55,7 @@ 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 +67,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 +84,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 +102,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 +117,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 +234,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 +246,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 +266,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 +278,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 +298,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 +321,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 +334,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); @@ -355,8 +356,10 @@ static void copy(File from, Charset charset, Appendable to) throws IOException { * @deprecated Prefer {@code asCharSink(to, charset, FileWriteMode.APPEND).write(from)}. This * method is scheduled to be removed in October 2019. */ - @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 +370,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 +394,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 +414,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 +443,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 +460,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 +490,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 +515,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,7 +542,6 @@ 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 @@ -582,14 +573,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 +596,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 +618,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 +644,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 +666,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 +689,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 +732,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) { @@ -794,11 +788,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 +811,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,7 +840,6 @@ public static String getNameWithoutExtension(String file) { * * @since 23.5 */ - @Beta public static Traverser fileTraverser() { return Traverser.forTree(FILE_TREE); } @@ -872,7 +865,6 @@ public Iterable successors(File file) { * * @since 15.0 */ - @Beta public static Predicate isDirectory() { return FilePredicate.IS_DIRECTORY; } @@ -882,7 +874,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..383163d4977c --- /dev/null +++ b/android/guava/src/com/google/common/io/IgnoreJRERequirement.java @@ -0,0 +1,29 @@ +/* + * 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.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}) +@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..ad9cf179ee3c 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 { 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..d51a9ceabeb0 --- /dev/null +++ b/android/guava/src/com/google/common/io/MoreFiles.java @@ -0,0 +1,866 @@ +/* + * 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 + /* + * If users use this when they shouldn't, we hope that NewApi will catch subsequent + * Stream calls. + * + * Anyway, this is just an override that is no more dangerous than the supermethod. + */ + "Java7ApiChecker", + }) + @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) { + final 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) { + final 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..7f404b5da4aa 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 @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..0e7bab9065bd 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 { 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..386bad6f3397 100644 --- a/android/guava/src/com/google/common/io/ReaderInputStream.java +++ b/android/guava/src/com/google/common/io/ReaderInputStream.java @@ -17,8 +17,10 @@ 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 com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.primitives.UnsignedBytes; import java.io.IOException; import java.io.InputStream; @@ -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; @@ -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..6a19e8ea264a 100644 --- a/android/guava/src/com/google/common/io/Resources.java +++ b/android/guava/src/com/google/common/io/Resources.java @@ -17,9 +17,8 @@ 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; @@ -28,21 +27,21 @@ import java.io.OutputStream; import java.net.URL; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; 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 */ 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..6a65e39d2572 --- /dev/null +++ b/android/guava/src/com/google/common/io/TempFileCreator.java @@ -0,0 +1,313 @@ +/* + * 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(); + } + + // Don't merge these catch() blocks, let alone use ReflectiveOperationException directly: + // b/65343391 + } catch (NoSuchFieldException e) { + // The JELLY_BEAN field doesn't exist because we're running on a version before Jelly Bean :) + return new ThrowingCreator(); + } catch (ClassNotFoundException e) { + // Should be impossible, but we want to return *something* so that class init succeeds. + return new ThrowingCreator(); + } catch (IllegalAccessException e) { + // ditto + 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 shouldBeImpossible) { + return fromSystemProperty; + } catch (IllegalAccessException shouldBeImpossible) { + /* + * We don't merge these into `catch (ReflectiveOperationException ...)` or an equivalent + * multicatch because ReflectiveOperationException isn't available under Android: + * b/124188803 + */ + 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..e1a7a10d4b98 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() {} diff --git a/android/guava/src/com/google/common/math/BigIntegerMath.java b/android/guava/src/com/google/common/math/BigIntegerMath.java index bf443e9b145b..7220596ae3a9 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; @@ -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 diff --git a/android/guava/src/com/google/common/math/DoubleMath.java b/android/guava/src/com/google/common/math/DoubleMath.java index 05394408608f..86ac100efc8c 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; @@ -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..4d9e99fd0df0 --- /dev/null +++ b/android/guava/src/com/google/common/math/IgnoreJRERequirement.java @@ -0,0 +1,29 @@ +/* + * 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.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}) +@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..ad2323a60988 100644 --- a/android/guava/src/com/google/common/math/IntMath.java +++ b/android/guava/src/com/google/common/math/IntMath.java @@ -25,7 +25,6 @@ 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; @@ -49,8 +48,6 @@ */ @GwtCompatible(emulated = true) 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 +59,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 +74,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 +85,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 +117,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 +135,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 +158,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 +170,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) { @@ -294,9 +288,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) { @@ -312,7 +305,8 @@ private static int sqrtFloor(int x) { * @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 +331,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; @@ -487,6 +481,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) { @@ -531,7 +527,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 +537,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 +547,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 +557,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) { @@ -719,7 +713,6 @@ public static int mean(int x, int y) { * @since 20.0 */ @GwtIncompatible // TODO - @Beta public static boolean isPrime(int n) { return LongMath.isPrime(n); } diff --git a/android/guava/src/com/google/common/math/LinearTransformation.java b/android/guava/src/com/google/common/math/LinearTransformation.java index 485b04660902..379898d51842 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; @@ -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..b83fa1bede1d 100644 --- a/android/guava/src/com/google/common/math/LongMath.java +++ b/android/guava/src/com/google/common/math/LongMath.java @@ -25,11 +25,9 @@ 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 java.math.BigInteger; import java.math.RoundingMode; @@ -50,8 +48,6 @@ */ @GwtCompatible(emulated = true) 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 +59,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 +74,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 +88,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 +119,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 +137,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 +161,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 +173,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 @@ -309,7 +302,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 +322,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,9 +360,8 @@ 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(); } /** @@ -403,7 +394,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 +415,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 } @@ -542,7 +533,8 @@ public static long gcd(long a, long b) { * * @throws ArithmeticException if {@code a + b} overflows in signed {@code long} arithmetic */ - @GwtIncompatible // TODO + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static long checkedAdd(long a, long b) { long result = a + b; checkNoOverflow((a ^ b) < 0 | (a ^ result) >= 0, "checkedAdd", a, b); @@ -554,7 +546,8 @@ public static long checkedAdd(long a, long b) { * * @throws ArithmeticException if {@code a - b} overflows in signed {@code long} arithmetic */ - @GwtIncompatible // TODO + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static long checkedSubtract(long a, long b) { long result = a - b; checkNoOverflow((a ^ b) >= 0 | (a ^ result) >= 0, "checkedSubtract", a, b); @@ -566,6 +559,8 @@ public static long checkedSubtract(long a, long b) { * * @throws ArithmeticException if {@code a * b} overflows in signed {@code long} arithmetic */ + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static long checkedMultiply(long a, long b) { // Hacker's Delight, Section 2-12 int leadingZeros = @@ -600,6 +595,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) { @@ -647,7 +644,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 +663,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 +682,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 +713,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) { @@ -740,7 +741,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 +958,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 +980,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 +1001,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 +1116,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 +1242,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 +1252,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 +1262,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 +1321,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 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..5985f8498651 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 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; } diff --git a/android/guava/src/com/google/common/math/PairedStatsAccumulator.java b/android/guava/src/com/google/common/math/PairedStatsAccumulator.java index a9884959a04a..e8a772e9539b 100644 --- a/android/guava/src/com/google/common/math/PairedStatsAccumulator.java +++ b/android/guava/src/com/google/common/math/PairedStatsAccumulator.java @@ -19,8 +19,8 @@ import static java.lang.Double.NaN; 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. 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: + * + *

      + *
    • 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/math/Quantiles.java b/android/guava/src/com/google/common/math/Quantiles.java index 7aac58f077ea..37ba5a93f68b 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; @@ -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..f902ba777048 100644 --- a/android/guava/src/com/google/common/math/Stats.java +++ b/android/guava/src/com/google/common/math/Stats.java @@ -24,15 +24,19 @@ 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 java.util.Iterator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +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,89 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +424,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; } diff --git a/android/guava/src/com/google/common/math/StatsAccumulator.java b/android/guava/src/com/google/common/math/StatsAccumulator.java index 54468cf4f963..1a2b528225be 100644 --- a/android/guava/src/com/google/common/math/StatsAccumulator.java +++ b/android/guava/src/com/google/common/math/StatsAccumulator.java @@ -20,9 +20,12 @@ import static java.lang.Double.NaN; 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,43 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + @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..4d66936c48d4 100644 --- a/android/guava/src/com/google/common/net/HostAndPort.java +++ b/android/guava/src/com/google/common/net/HostAndPort.java @@ -18,13 +18,14 @@ 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.CharMatcher; import com.google.common.base.Objects; import com.google.common.base.Strings; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import java.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An immutable representation of a host and port. @@ -59,7 +60,6 @@ * @author Paul Marks * @since 10.0 */ -@Beta @Immutable @GwtCompatible public final class HostAndPort implements Serializable { @@ -162,6 +162,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; @@ -188,8 +189,11 @@ public static HostAndPort fromString(String hostPortString) { 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); + // Java accepts leading plus signs. We don't want to. + checkArgument( + !portString.startsWith("+") && CharMatcher.ascii().matchesAllOf(portString), + "Unparseable port number: %s", + hostPortString); try { port = Integer.parseInt(portString); } catch (NumberFormatException e) { @@ -204,19 +208,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,13 +273,14 @@ 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; } 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..bbfb1e5367d8 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; + final 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..c3b3d91f3c40 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,7 +72,6 @@ * @author Catherine Berry * @since 5.0 */ -@Beta @GwtCompatible(emulated = true) @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); } /** @@ -581,25 +643,13 @@ public InternetDomainName child(String leftParts) { */ 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..49b10c651330 100644 --- a/android/guava/src/com/google/common/net/MediaType.java +++ b/android/guava/src/com/google/common/net/MediaType.java @@ -16,16 +16,14 @@ 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.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 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; @@ -37,15 +35,15 @@ 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.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 +70,6 @@ * @since 12.0 * @author Gregory Kick */ -@Beta @GwtCompatible @Immutable public final class MediaType { @@ -105,7 +102,7 @@ public final class MediaType { private static final String WILDCARD = "*"; - private static final Map KNOWN_TYPES = Maps.newHashMap(); + private static final Map knownTypes = Maps.newHashMap(); private static MediaType createConstant(String type, String subtype) { MediaType mediaType = @@ -121,7 +118,7 @@ private static MediaType createConstantUtf8(String type, String subtype) { } private static MediaType addKnownType(MediaType mediaType) { - KNOWN_TYPES.put(mediaType, mediaType); + knownTypes.put(mediaType, mediaType); return mediaType; } @@ -156,6 +153,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 +170,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. @@ -398,7 +405,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 +458,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 +511,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 +545,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 +803,11 @@ 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; + @LazyInit private @Nullable Optional parsedCharset; private MediaType(String type, String subtype, ImmutableListMultimap parameters) { this.type = type; @@ -791,14 +831,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); } /** @@ -873,7 +906,7 @@ public MediaType withParameters(String attribute, Iterable values) { 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); + return MoreObjects.firstNonNull(knownTypes.get(mediaType), mediaType); } /** @@ -895,7 +928,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); @@ -974,7 +1007,7 @@ 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); + return MoreObjects.firstNonNull(knownTypes.get(mediaType), mediaType); } /** @@ -1048,21 +1081,20 @@ 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; + consumeSeparator(tokenizer, '='); + String value; if ('"' == tokenizer.previewChar()) { tokenizer.consumeCharacter('"'); StringBuilder valueBuilder = new StringBuilder(); @@ -1087,6 +1119,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 +1133,7 @@ private static final class Tokenizer { this.input = input; } + @CanIgnoreReturnValue String consumeTokenIfPresent(CharMatcher matcher) { checkState(hasMore()); int startPosition = position; @@ -1117,6 +1156,7 @@ char consumeCharacter(CharMatcher matcher) { return c; } + @CanIgnoreReturnValue char consumeCharacter(char c) { checkState(hasMore()); checkState(previewChar() == c); @@ -1135,7 +1175,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) { @@ -1184,14 +1224,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..4232a251f582 100644 --- a/android/guava/src/com/google/common/primitives/Booleans.java +++ b/android/guava/src/com/google/common/primitives/Booleans.java @@ -18,9 +18,10 @@ import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; +import static java.lang.Math.min; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -29,7 +30,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 +43,6 @@ * @since 1.0 */ @GwtCompatible -@ElementTypesAreNonnullByDefault public final class Booleans { private Booleans() {} @@ -75,12 +75,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,12 +87,11 @@ 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; } @@ -102,7 +100,7 @@ public static Comparator falseFirst() { * 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. + *

    Java 8+ users: use {@link Boolean#hashCode(boolean)} instead. * * @param value a primitive {@code boolean} value * @return a hash code for the 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,9 @@ private enum LexicographicalComparator implements Comparator { @Override public int compare(boolean[] left, boolean[] right) { - int minLength = Math.min(left.length, right.length); + int minLength = 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 +370,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 @@ -409,14 +419,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 +438,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 +469,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; } @@ -510,7 +520,6 @@ boolean[] toBooleanArray() { * * @since 16.0 */ - @Beta public static int countTrue(boolean... values) { int count = 0; for (boolean value : values) { @@ -551,4 +560,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..871159dab7ee 100644 --- a/android/guava/src/com/google/common/primitives/Bytes.java +++ b/android/guava/src/com/google/common/primitives/Bytes.java @@ -27,7 +27,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,7 +44,6 @@ // TODO(kevinb): how to prevent warning on UnsignedBytes when building GWT // javadoc? @GwtCompatible -@ElementTypesAreNonnullByDefault public final class Bytes { private Bytes() {} @@ -52,7 +51,7 @@ 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. + *

    Java 8+ users: use {@link Byte#hashCode(byte)} instead. * * @param value a primitive {@code byte} value * @return a hash code for the value @@ -156,13 +155,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 +172,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 +236,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 */ @@ -271,13 +282,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 +300,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 +331,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; } @@ -396,4 +407,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..d509de230d48 100644 --- a/android/guava/src/com/google/common/primitives/Chars.java +++ b/android/guava/src/com/google/common/primitives/Chars.java @@ -19,9 +19,9 @@ 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.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -30,7 +30,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 @@ -46,14 +46,13 @@ * @since 1.0 */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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. + *

    Java 8+ users: use {@link Character#BYTES} instead. */ public static final int BYTES = Character.SIZE / Byte.SIZE; @@ -61,7 +60,7 @@ private Chars() {} * 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. + *

    Java 8+ users: use {@link Character#hashCode(char)} instead. * * @param value a primitive {@code char} value * @return a hash code for the value @@ -106,7 +105,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 +113,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 +258,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 +269,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 +286,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 +402,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 +497,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 +556,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 */ @@ -541,14 +602,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 +621,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 +652,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; } diff --git a/android/guava/src/com/google/common/primitives/Doubles.java b/android/guava/src/com/google/common/primitives/Doubles.java index ce5df2e394db..3cfd49968480 100644 --- a/android/guava/src/com/google/common/primitives/Doubles.java +++ b/android/guava/src/com/google/common/primitives/Doubles.java @@ -22,10 +22,10 @@ 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.base.Converter; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -34,7 +34,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 @@ -47,14 +49,13 @@ * @since 1.0 */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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. + *

    Java 8+ users: use {@link Double#BYTES} instead. * * @since 10.0 */ @@ -64,7 +65,7 @@ private Doubles() {} * 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. + *

    Java 8+ users: use {@link Double#hashCode(double)} instead. * * @param value a primitive {@code double} value * @return a hash code for the value @@ -90,6 +91,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,7 +100,7 @@ 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. + *

    Java 8+ users: use {@link Double#isFinite(double)} instead. * * @since 10.0 */ @@ -247,13 +249,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 +274,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 +291,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) { @@ -318,7 +331,6 @@ private Object readResolve() { * * @since 16.0 */ - @Beta public static Converter stringConverter() { return DoubleConverter.INSTANCE; } @@ -467,6 +479,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 +569,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. * @@ -554,14 +618,25 @@ public Double get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + @SuppressWarnings("Java7ApiChecker") + /* + * 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 +648,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 +679,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; } @@ -701,10 +776,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..04d96512211d 100644 --- a/android/guava/src/com/google/common/primitives/DoublesMethodsForWeb.java +++ b/android/guava/src/com/google/common/primitives/DoublesMethodsForWeb.java @@ -21,5 +21,4 @@ * version. */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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..bc70bd76e968 100644 --- a/android/guava/src/com/google/common/primitives/Floats.java +++ b/android/guava/src/com/google/common/primitives/Floats.java @@ -22,10 +22,10 @@ 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.base.Converter; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -34,7 +34,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 @@ -47,14 +47,13 @@ * @since 1.0 */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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. + *

    Java 8+ users: use {@link Float#BYTES} instead. * * @since 10.0 */ @@ -64,7 +63,7 @@ private Floats() {} * 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. + *

    Java 8+ users: use {@link Float#hashCode(float)} instead. * * @param value a primitive {@code float} value * @return a hash code for the value @@ -87,6 +86,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,7 +95,7 @@ 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. + *

    Java 8+ users: use {@link Float#isFinite(float)} instead. * * @since 10.0 */ @@ -244,13 +244,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 +269,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 +286,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) { @@ -315,7 +326,6 @@ private Object readResolve() { * * @since 16.0 */ - @Beta public static Converter stringConverter() { return FloatConverter.INSTANCE; } @@ -464,6 +474,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 +564,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 */ @@ -548,13 +610,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 +628,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 +659,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; } @@ -660,10 +722,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..acdb42f275b1 100644 --- a/android/guava/src/com/google/common/primitives/FloatsMethodsForWeb.java +++ b/android/guava/src/com/google/common/primitives/FloatsMethodsForWeb.java @@ -21,5 +21,4 @@ * version. */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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 54% rename from android/guava/src/com/google/common/primitives/Platform.java rename to android/guava/src/com/google/common/primitives/IgnoreJRERequirement.java index 83b248517993..0013432f9912 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,16 @@ package com.google.common.primitives; -import com.google.common.annotations.GwtCompatible; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +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}) +@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..d05637fa239c 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,19 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +212,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 +224,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 +236,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 +248,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 +263,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 +272,30 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +334,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 +422,32 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + // 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 +467,16 @@ public ImmutableDoubleArray subArray(int startIndex, int endIndex) { : new ImmutableDoubleArray(array, start + startIndex, start + endIndex); } + @SuppressWarnings("Java7ApiChecker") + @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. @@ -425,7 +500,7 @@ 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 +513,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 +532,20 @@ public List subList(int fromIndex, int toIndex) { return parent.subArray(fromIndex, toIndex).asList(); } + // The default List spliterator is not efficiently splittable + @Override + @SuppressWarnings("Java7ApiChecker") + /* + * 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 +585,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; } diff --git a/android/guava/src/com/google/common/primitives/ImmutableIntArray.java b/android/guava/src/com/google/common/primitives/ImmutableIntArray.java index 8f972c49b4fc..5b26c5b7d3eb 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,19 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +209,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 +221,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 +233,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 +245,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 +260,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 +269,30 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +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 ImmutableIntArray build() { return count == 0 ? EMPTY : new ImmutableIntArray(array, 0, count); } @@ -378,6 +417,32 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + // 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 +462,16 @@ public ImmutableIntArray subArray(int startIndex, int endIndex) { : new ImmutableIntArray(array, start + startIndex, start + endIndex); } + @SuppressWarnings("Java7ApiChecker") + @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. @@ -420,7 +495,7 @@ 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 +508,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 +527,20 @@ public List subList(int fromIndex, int toIndex) { return parent.subArray(fromIndex, toIndex).asList(); } + // The default List spliterator is not efficiently splittable + @Override + @SuppressWarnings("Java7ApiChecker") + /* + * 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 +580,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; } diff --git a/android/guava/src/com/google/common/primitives/ImmutableLongArray.java b/android/guava/src/com/google/common/primitives/ImmutableLongArray.java index 4ebf5b406fb9..726238c9f0f0 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,19 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +211,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 +223,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 +235,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 +247,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 +262,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 +271,30 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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 +333,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 +419,32 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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) + */ + @SuppressWarnings("Java7ApiChecker") + // 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 +464,16 @@ public ImmutableLongArray subArray(int startIndex, int endIndex) { : new ImmutableLongArray(array, start + startIndex, start + endIndex); } + @SuppressWarnings("Java7ApiChecker") + @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 @@ -422,7 +497,7 @@ 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 +510,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 +529,20 @@ public List subList(int fromIndex, int toIndex) { return parent.subArray(fromIndex, toIndex).asList(); } + // The default List spliterator is not efficiently splittable + @Override + @SuppressWarnings("Java7ApiChecker") + /* + * 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 +582,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; } diff --git a/android/guava/src/com/google/common/primitives/Ints.java b/android/guava/src/com/google/common/primitives/Ints.java index e07bdd88d93e..ee81ad6c9f59 100644 --- a/android/guava/src/com/google/common/primitives/Ints.java +++ b/android/guava/src/com/google/common/primitives/Ints.java @@ -19,10 +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.base.Converter; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -31,7 +31,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 @@ -44,14 +46,13 @@ * @since 1.0 */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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. + *

    Java 8+ users: use {@link Integer#BYTES} instead. */ public static final int BYTES = Integer.SIZE / Byte.SIZE; @@ -66,7 +67,7 @@ private Ints() {} * 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. + *

    Java 8+ users: use {@link Integer#hashCode(int)} instead. * * @param value a primitive {@code int} value * @return a hash code for the value @@ -111,7 +112,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 +120,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 +263,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 +285,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 +302,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 +353,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) { @@ -372,7 +388,6 @@ private Object readResolve() { * * @since 16.0 */ - @Beta public static Converter stringConverter() { return IntConverter.INSTANCE; } @@ -439,10 +454,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 +527,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 +640,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. * @@ -594,13 +689,24 @@ public Integer get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + @SuppressWarnings("Java7ApiChecker") + /* + * 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 +718,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 +749,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; } @@ -697,8 +803,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 +812,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 +824,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 +836,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..cb87bd2929f8 100644 --- a/android/guava/src/com/google/common/primitives/IntsMethodsForWeb.java +++ b/android/guava/src/com/google/common/primitives/IntsMethodsForWeb.java @@ -21,5 +21,4 @@ * version. */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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..a1130a3b128a 100644 --- a/android/guava/src/com/google/common/primitives/Longs.java +++ b/android/guava/src/com/google/common/primitives/Longs.java @@ -19,9 +19,9 @@ 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.base.Converter; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -30,7 +30,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,14 +45,13 @@ * @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. + *

    Java 8+ users: use {@link Long#BYTES} instead. */ public static final int BYTES = Long.SIZE / Byte.SIZE; @@ -69,7 +70,7 @@ private Longs() {} * 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. + *

    Java 8+ users: use {@link Long#hashCode(long)} instead. * * @param value a primitive {@code long} value * @return a hash code for the value @@ -82,7 +83,7 @@ public static int hashCode(long 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 +91,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 +230,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 +250,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 +267,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 +366,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 +375,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 +387,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 +399,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 +442,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) { @@ -467,7 +477,6 @@ private Object readResolve() { * * @since 16.0 */ - @Beta public static Converter stringConverter() { return LongConverter.INSTANCE; } @@ -538,7 +547,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 +615,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 +702,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. * @@ -690,13 +751,24 @@ public Long get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + @SuppressWarnings("Java7ApiChecker") + /* + * 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 +780,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 +811,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; } 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..8c9e0b19e463 100644 --- a/android/guava/src/com/google/common/primitives/Shorts.java +++ b/android/guava/src/com/google/common/primitives/Shorts.java @@ -19,10 +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.base.Converter; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -31,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 short} primitives, that are not already found in @@ -44,14 +44,13 @@ * @since 1.0 */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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. + *

    Java 8+ users: use {@link Short#BYTES} instead. */ public static final int BYTES = Short.SIZE / Byte.SIZE; @@ -66,7 +65,7 @@ private Shorts() {} * 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. + *

    Java 8+ users: use {@link Short#hashCode(short)} instead. * * @param value a primitive {@code short} value * @return a hash code for the value @@ -110,7 +109,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 +117,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 +266,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 +278,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 +295,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 +346,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) { @@ -372,7 +381,6 @@ private Object readResolve() { * * @since 16.0 */ - @Beta public static Converter stringConverter() { return ShortConverter.INSTANCE; } @@ -444,7 +452,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 +520,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 +607,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 */ @@ -593,13 +653,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 +671,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 +702,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; } diff --git a/android/guava/src/com/google/common/primitives/ShortsMethodsForWeb.java b/android/guava/src/com/google/common/primitives/ShortsMethodsForWeb.java index bb0ff103ce2f..c36276838cc6 100644 --- a/android/guava/src/com/google/common/primitives/ShortsMethodsForWeb.java +++ b/android/guava/src/com/google/common/primitives/ShortsMethodsForWeb.java @@ -21,5 +21,4 @@ * version. */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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..669fcff2c5e3 100644 --- a/android/guava/src/com/google/common/primitives/UnsignedBytes.java +++ b/android/guava/src/com/google/common/primitives/UnsignedBytes.java @@ -19,11 +19,15 @@ import static com.google.common.base.Preconditions.checkPositionIndexes; 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 java.lang.reflect.Field; import java.nio.ByteOrder; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.Arrays; import java.util.Comparator; import sun.misc.Unsafe; @@ -43,8 +47,8 @@ * @author Louis Wasserman * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible -@ElementTypesAreNonnullByDefault public final class UnsignedBytes { private UnsignedBytes() {} @@ -68,7 +72,7 @@ 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. + *

    Java 8+ users: use {@link Byte#toUnsignedInt(byte)} instead. * * @since 6.0 */ @@ -167,7 +171,6 @@ public static byte max(byte... array) { * * @since 13.0 */ - @Beta public static String toString(byte x) { return toString(x, 10); } @@ -182,7 +185,6 @@ 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, @@ -201,7 +203,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 +220,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); @@ -292,6 +292,7 @@ static class LexicographicalComparatorHolder { static final Comparator BEST_COMPARATOR = getBestComparator(); + @SuppressWarnings("SunApi") // b/345822163 @VisibleForTesting enum UnsafeComparator implements Comparator { INSTANCE; @@ -336,19 +337,19 @@ 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() { + 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)) { @@ -358,14 +359,16 @@ 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()); } } @Override + // Long.compareUnsigned is available under Android, which is what we really care about. + @SuppressWarnings("Java7ApiChecker") 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 +382,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); } /* diff --git a/android/guava/src/com/google/common/primitives/UnsignedInteger.java b/android/guava/src/com/google/common/primitives/UnsignedInteger.java index 0b30cef3f4c6..0bc2eb467a74 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. @@ -39,7 +40,6 @@ * @since 11.0 */ @GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault 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..644ef2da75c1 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; @@ -196,6 +195,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 +274,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 +288,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 +330,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 +344,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 +368,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 +378,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..7e1c82815525 100644 --- a/android/guava/src/com/google/common/primitives/UnsignedLong.java +++ b/android/guava/src/com/google/common/primitives/UnsignedLong.java @@ -21,7 +21,7 @@ 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. @@ -38,7 +38,6 @@ * @since 11.0 */ @GwtCompatible(serializable = true) -@ElementTypesAreNonnullByDefault public final class UnsignedLong extends Number implements Comparable, Serializable { private static final long UNSIGNED_MASK = 0x7fffffffffffffffL; @@ -242,7 +241,7 @@ public int hashCode() { } @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..ac9b2c5c750d 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)); } @@ -239,7 +238,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 +273,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 +308,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 +323,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} @@ -437,7 +436,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 +446,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..34f9eb82ffbd 100644 --- a/android/guava/src/com/google/common/reflect/ClassPath.java +++ b/android/guava/src/com/google/common/reflect/ClassPath.java @@ -20,10 +20,8 @@ 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; @@ -51,7 +49,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 +90,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 +163,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 +201,6 @@ public ImmutableSet getTopLevelClassesRecursive(String packageName) { * * @since 14.0 */ - @Beta public static class ResourceInfo { private final File file; private final String resourceName; @@ -287,7 +276,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 +296,6 @@ public String toString() { * * @since 14.0 */ - @Beta public static final class ClassInfo extends ResourceInfo { private final String className; @@ -561,7 +549,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 +576,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(); } 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..a56902603307 --- /dev/null +++ b/android/guava/src/com/google/common/reflect/IgnoreJRERequirement.java @@ -0,0 +1,29 @@ +/* + * 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.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}) +@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..18bce983963d 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,18 +28,17 @@ * @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<>(); } /** @@ -58,7 +57,6 @@ public static Builder builder() { * * @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..f33f207f69d9 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 @@ -49,18 +47,27 @@ * 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(); } @@ -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..3904ecd699ef 100644 --- a/android/guava/src/com/google/common/reflect/MutableTypeToInstanceMap.java +++ b/android/guava/src/com/google/common/reflect/MutableTypeToInstanceMap.java @@ -16,8 +16,6 @@ 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; @@ -28,7 +26,8 @@ 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 = Maps.newHashMap(); @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..0d04e847df88 100644 --- a/android/guava/src/com/google/common/reflect/TypeParameter.java +++ b/android/guava/src/com/google/common/reflect/TypeParameter.java @@ -16,10 +16,9 @@ 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: @@ -34,8 +33,6 @@ * @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..139cdd5f934d 100644 --- a/android/guava/src/com/google/common/reflect/TypeResolver.java +++ b/android/guava/src/com/google/common/reflect/TypeResolver.java @@ -19,7 +19,6 @@ 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; @@ -35,7 +34,7 @@ import java.util.Map.Entry; 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 +42,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 +50,6 @@ * @author Ben Yu * @since 15.0 */ -@Beta -@ElementTypesAreNonnullByDefault public final class TypeResolver { private final TypeTable typeTable; @@ -123,7 +120,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; } @@ -296,11 +293,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 @@ -414,7 +411,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 +425,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 +501,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 +525,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; } @@ -563,7 +559,7 @@ public int hashCode() { } @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 +574,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..77d613eb66f9 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; @@ -32,6 +32,7 @@ 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; @@ -47,7 +48,7 @@ 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. @@ -89,7 +90,7 @@ * *

    {@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}. @@ -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}. */ @@ -240,7 +239,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)); } /** @@ -305,8 +304,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 +322,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 +574,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 +668,7 @@ public String toString() { */ public class TypeSet extends ForwardingSet> implements Serializable { - @CheckForNull private transient ImmutableSet> types; + private transient @Nullable ImmutableSet> types; TypeSet() {} @@ -718,7 +714,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 +742,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 +759,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 +824,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); @@ -1103,7 +1091,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 +1138,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 +1310,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 +1329,7 @@ Iterable> getInterfaces(TypeToken type) { } @Override - @CheckForNull - TypeToken getSuperclass(TypeToken type) { + @Nullable TypeToken getSuperclass(TypeToken type) { return type.getGenericSuperclass(); } }; @@ -1361,8 +1347,7 @@ Iterable> getInterfaces(Class type) { } @Override - @CheckForNull - Class getSuperclass(Class type) { + @Nullable Class getSuperclass(Class type) { return type.getSuperclass(); } }; @@ -1412,11 +1397,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 +1413,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 +1430,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 +1451,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..3cff3f90f809 100644 --- a/android/guava/src/com/google/common/reflect/TypeVisitor.java +++ b/android/guava/src/com/google/common/reflect/TypeVisitor.java @@ -21,7 +21,7 @@ import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; 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 @@ -54,7 +54,6 @@ * * @author Ben Yu */ -@ElementTypesAreNonnullByDefault abstract class TypeVisitor { private final Set visited = Sets.newHashSet(); diff --git a/android/guava/src/com/google/common/reflect/Types.java b/android/guava/src/com/google/common/reflect/Types.java index 0dc327d3d1b6..0e9885168472 100644 --- a/android/guava/src/com/google/common/reflect/Types.java +++ b/android/guava/src/com/google/common/reflect/Types.java @@ -20,7 +20,6 @@ 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; @@ -45,26 +44,16 @@ import java.util.Collection; import java.util.Map.Entry; 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 +78,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 +98,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 +113,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 +155,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 +163,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 +194,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,7 +236,7 @@ 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()); @@ -265,11 +249,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 +273,7 @@ public Type getRawType() { } @Override - @CheckForNull - public Type getOwnerType() { + public @Nullable Type getOwnerType() { return ownerType; } @@ -303,7 +286,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,7 +299,7 @@ public int hashCode() { } @Override - public boolean equals(@CheckForNull Object other) { + public boolean equals(@Nullable Object other) { if (!(other instanceof ParameterizedType)) { return false; } @@ -331,8 +314,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( @@ -366,6 +348,7 @@ private static TypeVariable newTypeVariableImp *

    This workaround should be removed at a distant future time when we no longer support Java * versions earlier than 8. */ + @SuppressWarnings("removal") // b/318391980 private static final class TypeVariableInvocationHandler implements InvocationHandler { private static final ImmutableMap typeVariableMethods; @@ -382,7 +365,7 @@ private static final class TypeVariableInvocationHandler implements InvocationHa builder.put(method.getName(), method); } } - typeVariableMethods = builder.build(); + typeVariableMethods = builder.buildKeepingLast(); } private final TypeVariableImpl typeVariableImpl; @@ -392,8 +375,7 @@ 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); @@ -449,7 +431,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 +480,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())) @@ -605,14 +587,7 @@ String typeName(Type type) { 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) { + } catch (InvocationTargetException | IllegalAccessException e) { throw new RuntimeException(e); } } @@ -679,14 +654,13 @@ 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. */ static final class NativeTypeVariableEquals { static final boolean NATIVE_TYPE_VARIABLE_ONLY = 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..c15f6cb658e2 100644 --- a/android/guava/src/com/google/common/util/concurrent/AbstractCatchingFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/AbstractCatchingFuture.java @@ -17,22 +17,32 @@ 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 +@SuppressWarnings({ + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + "ShortCircuitBoolean", + "nullness", // TODO(b/147136275): Remove once our checker understands & and |. +}) +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, @@ -42,7 +52,7 @@ static ListenableFuture create( return future; } - static ListenableFuture create( + static ListenableFuture createAsync( ListenableFuture input, Class exceptionType, AsyncFunction fallback, @@ -56,9 +66,9 @@ 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 Class exceptionType; - @NullableDecl F fallback; + @LazyInit @Nullable ListenableFuture inputFuture; + @LazyInit @Nullable Class exceptionType; + @LazyInit @Nullable F fallback; AbstractCatchingFuture( ListenableFuture inputFuture, Class exceptionType, F fallback) { @@ -69,9 +79,9 @@ static ListenableFuture create( @Override public final void run() { - ListenableFuture localInputFuture = inputFuture; - Class localExceptionType = exceptionType; - F localFallback = fallback; + @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()) { @@ -102,12 +112,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 +137,7 @@ public final void run() { try { fallbackResult = doFallback(localFallback, castThrowable); } catch (Throwable t) { + restoreInterruptIfIsInterruptedException(t); setException(t); return; } finally { @@ -134,10 +149,10 @@ public final void run() { } @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) { @@ -158,16 +173,17 @@ protected String pendingToString() { /** Template method for subtypes to actually run the fallback. */ @ForOverride - @NullableDecl + @ParametricNullness 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); + abstract void setResult(@ParametricNullness T result); @Override protected final void afterDone() { - maybePropagateCancellationTo(inputFuture); + @RetainedLocalRef ListenableFuture localInputFuture = inputFuture; + maybePropagateCancellationTo(localInputFuture); this.inputFuture = null; this.exceptionType = null; this.fallback = null; @@ -177,7 +193,7 @@ protected final void afterDone() { * 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( @@ -209,7 +225,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 +235,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..d1dae236c13e 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,14 @@ package com.google.common.util.concurrent; -import com.google.common.annotations.Beta; +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 +32,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 = MoreExecutors.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 +129,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 +142,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 -> MoreExecutors.newThread(serviceName(), command).start(); } @Override @@ -183,19 +160,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 +186,9 @@ public final Service startAsync() { return this; } - /** @since 15.0 */ + /** + * @since 15.0 + */ @CanIgnoreReturnValue @Override public final Service stopAsync() { @@ -211,25 +196,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..1982fc794599 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,14 @@ 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 java.lang.Integer.toHexString; import static java.lang.System.identityHashCode; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater; -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,6 +30,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.ForOverride; import com.google.j2objc.annotations.ReflectionSupport; +import com.google.j2objc.annotations.RetainedLocalRef; +import java.lang.reflect.Field; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; @@ -42,8 +46,8 @@ 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; +import sun.misc.Unsafe; /** * An abstract implementation of {@link ListenableFuture}, intended for advanced users only. More @@ -64,15 +68,16 @@ * @author Luke Sandberg * @since 1.0 */ -// we use non-short circuiting comparisons intentionally -@SuppressWarnings({"ShortCircuitBoolean", "ShouldNotSubclass"}) +@SuppressWarnings({ + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + "ShortCircuitBoolean", + "nullness", // TODO(b/147136275): Remove once our checker understands & and |. +}) @GwtCompatible(emulated = true) @ReflectionSupport(value = ReflectionSupport.Level.FULL) -public abstract class AbstractFuture extends InternalFutureFailureAccess +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 final boolean GENERATE_CANCELLATION_CAUSES; static { // System.getProperty may throw if the security policy does not permit access. @@ -92,21 +97,24 @@ public abstract class AbstractFuture extends InternalFutureFailureAccess * 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,8 +142,7 @@ public final boolean cancel(boolean mayInterruptIfRunning) { } } - // Logger to log exceptions caught when running listeners. - private static final Logger log = Logger.getLogger(AbstractFuture.class.getName()); + static final LazyLogger log = new LazyLogger(AbstractFuture.class); // A heuristic for timed gets. If the remaining timeout is less than this, spin instead of // blocking. This value is what AbstractQueuedSynchronizer uses. @@ -150,24 +157,17 @@ public final boolean cancel(boolean mayInterruptIfRunning) { try { helper = new UnsafeAtomicHelper(); - } catch (Throwable unsafeFailure) { + } catch (Exception | Error unsafeFailure) { // sneaky checked exception 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 + // Catch absolutely everything and fall through to AtomicReferenceFieldUpdaterAtomicHelper. 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) { + 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. + // 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(); } @@ -182,9 +182,12 @@ public final boolean cancel(boolean mayInterruptIfRunning) { // 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); + log.get().log(Level.SEVERE, "UnsafeAtomicHelper is broken!", thrownUnsafeFailure); + log.get() + .log( + Level.SEVERE, + "AtomicReferenceFieldUpdaterAtomicHelper is broken!", + thrownAtomicReferenceFieldUpdaterFailure); } } @@ -192,8 +195,8 @@ public final boolean cancel(boolean mayInterruptIfRunning) { private static final class Waiter { static final Waiter TOMBSTONE = new Waiter(false /* ignored param */); - @NullableDecl volatile Thread thread; - @NullableDecl volatile Waiter next; + volatile @Nullable Thread thread; + volatile @Nullable Waiter next; /** * Constructor for the TOMBSTONE, avoids use of ATOMIC_HELPER in case this class is loaded @@ -208,7 +211,7 @@ private static final class Waiter { // non-volatile write to the next field. Should be made visible by subsequent CAS on waiters // field. - void setNext(Waiter next) { + void setNext(@Nullable Waiter next) { ATOMIC_HELPER.putNext(this, next); } @@ -265,17 +268,24 @@ private void removeWaiter(Waiter node) { /** 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; + 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; } + + Listener() { + this.task = null; + this.executor = null; + } } /** A special value to represent {@code null}. */ @@ -301,8 +311,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,16 +325,16 @@ 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 SetFuture implements Runnable { final AbstractFuture owner; final ListenableFuture future; @@ -341,7 +351,13 @@ public void run() { } Object valueToSet = getFutureValue(future); if (ATOMIC_HELPER.casValue(owner, this, valueToSet)) { - complete(owner); + complete( + owner, + /* + * Interruption doesn't propagate through a SetFuture chain (see getFutureValue), so + * don't invoke interruptTask. + */ + false); } } } @@ -363,13 +379,13 @@ public void run() { * argument. * */ - @NullableDecl private volatile Object value; + private volatile @Nullable Object value; /** All listeners. */ - @NullableDecl private volatile Listener listeners; + private volatile @Nullable Listener listeners; /** All waiting threads. */ - @NullableDecl private volatile Waiter waiters; + private volatile @Nullable Waiter waiters; /** Constructor for use by subclasses. */ protected AbstractFuture() {} @@ -385,7 +401,7 @@ protected AbstractFuture() {} // 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 + // 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. @@ -406,8 +422,10 @@ protected AbstractFuture() {} * * @throws CancellationException {@inheritDoc} */ + @SuppressWarnings("LabelledBreakTarget") // TODO(b/345814817): Maybe fix? @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 @@ -417,7 +435,7 @@ public V get(long timeout, TimeUnit unit) if (Thread.interrupted()) { throw new InterruptedException(); } - Object localValue = value; + @RetainedLocalRef Object localValue = value; if (localValue != null & !(localValue instanceof SetFuture)) { return getDoneValue(localValue); } @@ -460,7 +478,8 @@ public V get(long timeout, TimeUnit unit) } // re-read value, if we get here then we must have observed a TOMBSTONE while trying to add a // waiter. - return getDoneValue(value); + // requireNonNull is safe because value is always set before TOMBSTONE. + return getDoneValue(requireNonNull(value)); } // If we get here then we have remainingNanos < SPIN_THRESHOLD_NANOS and there is no node on the // waiters list @@ -483,7 +502,7 @@ public V get(long timeout, TimeUnit unit) // We over-waited for our timeout. message += " (plus "; long overWaitNanos = -remainingNanos; - long overWaitUnits = unit.convert(overWaitNanos, TimeUnit.NANOSECONDS); + long overWaitUnits = unit.convert(overWaitNanos, NANOSECONDS); long overWaitLeftoverNanos = overWaitNanos - unit.toNanos(overWaitUnits); boolean shouldShowExtraNanos = overWaitUnits == 0 || overWaitLeftoverNanos > SPIN_THRESHOLD_NANOS; @@ -519,11 +538,12 @@ 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; + @RetainedLocalRef Object localValue = value; if (localValue != null & !(localValue instanceof SetFuture)) { return getDoneValue(localValue); } @@ -554,19 +574,29 @@ public V get() throws InterruptedException, ExecutionException { } // re-read value, if we get here then we must have observed a TOMBSTONE while trying to add a // waiter. - return getDoneValue(value); + // requireNonNull is safe because value is always set before TOMBSTONE. + return getDoneValue(requireNonNull(value)); } /** Unboxes {@code obj}. Assumes that obj is not {@code null} or a {@link SetFuture}. */ + @ParametricNullness private 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; @@ -576,13 +606,13 @@ private V getDoneValue(Object obj) throws ExecutionException { @Override public boolean isDone() { - final Object localValue = value; + @RetainedLocalRef Object localValue = value; return localValue != null & !(localValue instanceof SetFuture); } @Override public boolean isCancelled() { - final Object localValue = value; + @RetainedLocalRef Object localValue = value; return localValue instanceof Cancellation; } @@ -605,7 +635,7 @@ 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) { // Try to delay allocating the exception. At this point we may still lose the CAS, but it is @@ -614,19 +644,25 @@ public boolean cancel(boolean mayInterruptIfRunning) { 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)) { rValue = true; - // We call interruptTask before calling complete(), which is consistent with - // FutureTask - if (mayInterruptIfRunning) { - abstractFuture.interruptTask(); - } - complete(abstractFuture); + complete(abstractFuture, mayInterruptIfRunning); if (localValue instanceof SetFuture) { // propagate cancellation to the future set in setfuture, this is racy, and we don't // care if we are successful or not. @@ -671,7 +707,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 +721,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; } @@ -741,10 +777,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); + complete(this, /* callInterruptTask= */ false); return true; } return false; @@ -769,7 +805,7 @@ protected boolean set(@NullableDecl V value) { protected boolean setException(Throwable throwable) { Object valueToSet = new Failure(checkNotNull(throwable)); if (ATOMIC_HELPER.casValue(this, null, valueToSet)) { - complete(this); + complete(this, /* callInterruptTask= */ false); return true; } return false; @@ -805,32 +841,41 @@ 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); + complete( + this, + /* + * Interruption doesn't propagate through a SetFuture chain (see getFutureValue), so + * don't invoke interruptTask. + */ + false); return true; } return false; } - SetFuture valueToSet = new SetFuture(this, future); + SetFuture valueToSet = new SetFuture<>(this, future); if (ATOMIC_HELPER.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) { + // Any Exception is either a RuntimeException or sneaky checked exception. + // // 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 // 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. @@ -874,7 +919,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 +932,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 +970,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 +979,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) { @@ -947,11 +999,26 @@ private static V getUninterruptibly(Future future) throws ExecutionExcept } /** Unblocks all threads and runs all listeners. */ - private static void complete(AbstractFuture future) { + private static void complete(AbstractFuture param, boolean callInterruptTask) { + // Declare a "true" local variable so that the Checker Framework will infer nullness. + AbstractFuture future = param; + 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 SetFuture 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. @@ -964,7 +1031,11 @@ private static void complete(AbstractFuture future) { while (next != null) { Listener curr = next; next = next.next; - Runnable task = curr.task; + /* + * requireNonNull is safe because the listener stack never contains TOMBSTONE until after + * clearListeners. + */ + Runnable task = requireNonNull(curr.task); if (task instanceof SetFuture) { SetFuture setFuture = (SetFuture) task; // We unwind setFuture specifically to avoid StackOverflowErrors in the case of long @@ -979,9 +1050,13 @@ private static void complete(AbstractFuture future) { 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 +1074,6 @@ private static void complete(AbstractFuture future) { * * @since 20.0 */ - @Beta @ForOverride protected void afterDone() {} @@ -1025,12 +1099,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,7 +1118,7 @@ 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) { + final void maybePropagateCancellationTo(@Nullable Future related) { if (related != null & isCancelled()) { related.cancel(wasInterrupted()); } @@ -1048,10 +1126,7 @@ final void maybePropagateCancellationTo(@NullableDecl Future related) { /** 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)); + Waiter head = ATOMIC_HELPER.gasWaiters(this, Waiter.TOMBSTONE); for (Waiter currentWaiter = head; currentWaiter != null; currentWaiter = currentWaiter.next) { currentWaiter.unpark(); } @@ -1061,17 +1136,14 @@ private void releaseWaiters() { * 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 = ATOMIC_HELPER.gasListeners(this, Listener.TOMBSTONE); Listener reversedList = onto; while (head != null) { Listener tmp = head; @@ -1098,7 +1170,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 +1181,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,7 +1197,7 @@ private void addPendingString(StringBuilder builder) { builder.append("PENDING"); - Object localValue = value; + @RetainedLocalRef Object localValue = value; if (localValue instanceof SetFuture) { builder.append(", setFuture=["); appendUserObject(builder, ((SetFuture) localValue).future); @@ -1136,7 +1206,9 @@ private void addPendingString(StringBuilder builder) { String pendingDescription; try { pendingDescription = Strings.emptyToNull(pendingToString()); - } catch (RuntimeException | StackOverflowError e) { + } catch (Exception | StackOverflowError e) { + // Any Exception is either a RuntimeException 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 +1227,7 @@ private void addPendingString(StringBuilder builder) { } } + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception private void addDoneString(StringBuilder builder) { try { V value = getUninterruptibly(this); @@ -1165,7 +1238,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 +1248,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,7 +1262,8 @@ 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 // arbitrary cycles using a thread local but this should be a good enough solution (it is also @@ -1200,7 +1274,9 @@ private void appendUserObject(StringBuilder builder, Object o) { } else { builder.append(o); } - } catch (RuntimeException | StackOverflowError e) { + } catch (Exception | StackOverflowError e) { + // Any Exception is either a RuntimeException 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,35 +1287,47 @@ 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); + // 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 abstract static class AtomicHelper { - /** Non volatile write of the thread to the {@link Waiter#thread} field. */ + /** 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); + /** 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 the {@link #waiters} field. */ - abstract boolean casWaiters(AbstractFuture future, Waiter expect, Waiter update); + /** Performs a CAS operation on the {@link AbstractFuture#waiters} field. */ + abstract boolean casWaiters( + AbstractFuture future, @Nullable Waiter expect, @Nullable 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 AbstractFuture#listeners} field. */ + abstract boolean casListeners( + AbstractFuture future, @Nullable Listener expect, Listener update); - /** Performs a CAS operation on the {@link #value} field. */ - abstract boolean casValue(AbstractFuture future, Object expect, Object update); + /** Performs a GAS operation on the {@link AbstractFuture#waiters} field. */ + abstract Waiter gasWaiters(AbstractFuture future, Waiter update); + + /** Performs a GAS operation on the {@link AbstractFuture#listeners} field. */ + abstract Listener gasListeners(AbstractFuture future, Listener update); + + /** Performs a CAS operation on the {@link AbstractFuture#value} field. */ + abstract boolean casValue(AbstractFuture future, @Nullable Object expect, Object update); } /** @@ -1248,9 +1336,9 @@ private abstract static class AtomicHelper { *

    Static initialization of this class will fail if the {@link sun.misc.Unsafe} object cannot * be accessed. */ - @SuppressWarnings("sunapi") + @SuppressWarnings("SunApi") // b/345822163 private static final class UnsafeAtomicHelper extends AtomicHelper { - static final sun.misc.Unsafe UNSAFE; + static final Unsafe UNSAFE; static final long LISTENERS_OFFSET; static final long WAITERS_OFFSET; static final long VALUE_OFFSET; @@ -1258,18 +1346,18 @@ private static final class UnsafeAtomicHelper extends AtomicHelper { static final long WAITER_NEXT_OFFSET; static { - sun.misc.Unsafe unsafe = null; + Unsafe unsafe = null; try { - unsafe = sun.misc.Unsafe.getUnsafe(); + unsafe = Unsafe.getUnsafe(); } catch (SecurityException tryReflectionInstead) { try { unsafe = AccessController.doPrivileged( - new PrivilegedExceptionAction() { + 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)) { @@ -1291,8 +1379,7 @@ public sun.misc.Unsafe run() throws Exception { 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); + } catch (NoSuchFieldException e) { throw new RuntimeException(e); } } @@ -1303,50 +1390,64 @@ void putThread(Waiter waiter, Thread newValue) { } @Override - void putNext(Waiter waiter, Waiter newValue) { + void putNext(Waiter waiter, @Nullable 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) { + boolean casWaiters(AbstractFuture future, @Nullable Waiter expect, @Nullable 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) { + boolean casListeners(AbstractFuture future, @Nullable 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) { + Listener gasListeners(AbstractFuture future, Listener update) { + while (true) { + Listener listener = future.listeners; + if (update == listener) { + return listener; + } + if (casListeners(future, listener, update)) { + return listener; + } + } + } + + @Override + Waiter gasWaiters(AbstractFuture future, Waiter update) { + while (true) { + Waiter waiter = future.waiters; + if (update == waiter) { + return waiter; + } + if (casWaiters(future, waiter, update)) { + return waiter; + } + } + } + + @Override + boolean casValue(AbstractFuture future, @Nullable 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; - } + private static final class AtomicReferenceFieldUpdaterAtomicHelper extends AtomicHelper { + private static final AtomicReferenceFieldUpdater waiterThreadUpdater = + newUpdater(Waiter.class, Thread.class, "thread"); + private static final AtomicReferenceFieldUpdater waiterNextUpdater = + newUpdater(Waiter.class, Waiter.class, "next"); + private static final AtomicReferenceFieldUpdater, Waiter> + waitersUpdater = waitersUpdaterFromWithinAbstractFuture(); + private static final AtomicReferenceFieldUpdater, Listener> + listenersUpdater = listenersUpdaterFromWithinAbstractFuture(); + private static final AtomicReferenceFieldUpdater, Object> + valueUpdater = valueUpdaterFromWithinAbstractFuture(); @Override void putThread(Waiter waiter, Thread newValue) { @@ -1354,26 +1455,54 @@ void putThread(Waiter waiter, Thread newValue) { } @Override - void putNext(Waiter waiter, Waiter newValue) { + void putNext(Waiter waiter, @Nullable Waiter newValue) { waiterNextUpdater.lazySet(waiter, newValue); } @Override - boolean casWaiters(AbstractFuture future, Waiter expect, Waiter update) { + boolean casWaiters(AbstractFuture future, @Nullable Waiter expect, @Nullable Waiter update) { return waitersUpdater.compareAndSet(future, expect, update); } @Override - boolean casListeners(AbstractFuture future, Listener expect, Listener update) { + boolean casListeners(AbstractFuture future, @Nullable Listener expect, Listener update) { return listenersUpdater.compareAndSet(future, expect, update); } @Override - boolean casValue(AbstractFuture future, Object expect, Object update) { + Listener gasListeners(AbstractFuture future, Listener update) { + return listenersUpdater.getAndSet(future, update); + } + + @Override + Waiter gasWaiters(AbstractFuture future, Waiter update) { + return waitersUpdater.getAndSet(future, update); + } + + @Override + boolean casValue(AbstractFuture future, @Nullable Object expect, Object update) { return valueUpdater.compareAndSet(future, expect, update); } } + /** Returns an {@link AtomicReferenceFieldUpdater} for {@link #waiters}. */ + private static AtomicReferenceFieldUpdater, Waiter> + waitersUpdaterFromWithinAbstractFuture() { + return newUpdater(AbstractFuture.class, Waiter.class, "waiters"); + } + + /** Returns an {@link AtomicReferenceFieldUpdater} for {@link #listeners}. */ + private static AtomicReferenceFieldUpdater, Listener> + listenersUpdaterFromWithinAbstractFuture() { + return newUpdater(AbstractFuture.class, Listener.class, "listeners"); + } + + /** Returns an {@link AtomicReferenceFieldUpdater} for {@link #value}. */ + private static AtomicReferenceFieldUpdater, Object> + valueUpdaterFromWithinAbstractFuture() { + return newUpdater(AbstractFuture.class, Object.class, "value"); + } + /** * {@link AtomicHelper} based on {@code synchronized} and volatile writes. * @@ -1387,12 +1516,12 @@ void putThread(Waiter waiter, Thread newValue) { } @Override - void putNext(Waiter waiter, Waiter newValue) { + void putNext(Waiter waiter, @Nullable Waiter newValue) { waiter.next = newValue; } @Override - boolean casWaiters(AbstractFuture future, Waiter expect, Waiter update) { + boolean casWaiters(AbstractFuture future, @Nullable Waiter expect, @Nullable Waiter update) { synchronized (future) { if (future.waiters == expect) { future.waiters = update; @@ -1403,7 +1532,7 @@ boolean casWaiters(AbstractFuture future, Waiter expect, Waiter update) { } @Override - boolean casListeners(AbstractFuture future, Listener expect, Listener update) { + boolean casListeners(AbstractFuture future, @Nullable Listener expect, Listener update) { synchronized (future) { if (future.listeners == expect) { future.listeners = update; @@ -1414,7 +1543,29 @@ boolean casListeners(AbstractFuture future, Listener expect, Listener update) } @Override - boolean casValue(AbstractFuture future, Object expect, Object update) { + Listener gasListeners(AbstractFuture future, Listener update) { + synchronized (future) { + Listener old = future.listeners; + if (old != update) { + future.listeners = update; + } + return old; + } + } + + @Override + Waiter gasWaiters(AbstractFuture future, Waiter update) { + synchronized (future) { + Waiter old = future.waiters; + if (old != update) { + future.waiters = update; + } + return old; + } + } + + @Override + boolean casValue(AbstractFuture future, @Nullable Object expect, Object update) { synchronized (future) { if (future.value == expect) { future.value = update; @@ -1426,7 +1577,7 @@ boolean casValue(AbstractFuture future, Object expect, Object update) { } 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/AbstractIdleService.java b/android/guava/src/com/google/common/util/concurrent/AbstractIdleService.java index 7416a9b655b2..a1b9e53abf82 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,10 @@ package com.google.common.util.concurrent; +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 +28,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"}. */ @@ -53,15 +57,13 @@ private final class DelegateService extends AbstractService { protected final void doStart() { MoreExecutors.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); } }); } @@ -70,15 +72,13 @@ public void run() { protected final void doStop() { MoreExecutors.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 +106,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 -> MoreExecutors.newThread(threadNameSupplier.get(), command).start(); } @Override @@ -129,19 +124,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 +150,9 @@ public final Service startAsync() { return this; } - /** @since 15.0 */ + /** + * @since 15.0 + */ @CanIgnoreReturnValue @Override public final Service stopAsync() { @@ -157,25 +160,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..60ec28ab87b9 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,14 @@ package com.google.common.util.concurrent; -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.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 +34,49 @@ * @author Chris Povirk * @since 14.0 */ -@Beta -@CanIgnoreReturnValue +@CheckReturnValue @GwtIncompatible +@J2ktIncompatible 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..d461740be33f 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 @@ -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,22 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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. @@ -126,13 +149,29 @@ public static Scheduler newFixedDelaySchedule( 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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. @@ -148,15 +187,16 @@ public static Scheduler newFixedRateSchedule( 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 +210,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. @@ -185,22 +225,31 @@ class Task implements Runnable { 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 +261,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); } }); } @@ -332,7 +378,7 @@ public Thread newThread(Runnable runnable) { } final 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 +424,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 +450,9 @@ public final Service startAsync() { return this; } - /** @since 15.0 */ + /** + * @since 15.0 + */ @CanIgnoreReturnValue @Override public final Service stopAsync() { @@ -406,30 +460,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 +526,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 +542,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 +574,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 +584,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 + public 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 +626,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 +634,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 +706,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 +733,16 @@ 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) + */ + @SuppressWarnings("Java7ApiChecker") + @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..7e7ef08ac477 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() { @@ -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); @@ -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..5a078eb48901 100644 --- a/android/guava/src/com/google/common/util/concurrent/AbstractTransformFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/AbstractTransformFuture.java @@ -17,20 +17,29 @@ 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( +@SuppressWarnings({ + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + "ShortCircuitBoolean", + "nullness", // TODO(b/147136275): Remove once our checker understands & and |. +}) +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) { @@ -40,7 +49,7 @@ static ListenableFuture create( return output; } - static ListenableFuture create( + static ListenableFuture create( ListenableFuture input, Function function, Executor executor) { checkNotNull(function); TransformFuture output = new TransformFuture<>(input, function); @@ -52,8 +61,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 +70,10 @@ static ListenableFuture create( } @Override + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception 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 +109,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 +127,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 +176,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 +212,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,7 +223,8 @@ private static final class AsyncTransformFuture @Override ListenableFuture doTransform( - AsyncFunction function, @NullableDecl I input) throws Exception { + AsyncFunction function, @ParametricNullness I input) + throws Exception { ListenableFuture outputFuture = function.apply(input); checkNotNull( outputFuture, @@ -230,7 +244,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 +252,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..eca2e4f713fc 100644 --- a/android/guava/src/com/google/common/util/concurrent/AggregateFutureState.java +++ b/android/guava/src/com/google/common/util/concurrent/AggregateFutureState.java @@ -15,6 +15,7 @@ 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; @@ -24,7 +25,7 @@ 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 @@ -37,16 +38,17 @@ */ @GwtCompatible(emulated = true) @ReflectionSupport(value = ReflectionSupport.Level.FULL) -abstract class AggregateFutureState extends AbstractFuture.TrustedFuture { +abstract class AggregateFutureState + extends AbstractFuture.TrustedFuture { // 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; + private volatile @Nullable Set seenExceptions = null; private volatile int remaining; 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; @@ -56,7 +58,7 @@ abstract class AggregateFutureState extends AbstractFuture.TrustedFutur new SafeAtomicHelper( newUpdater(AggregateFutureState.class, Set.class, "seenExceptions"), newUpdater(AggregateFutureState.class, "remaining")); - } catch (Throwable reflectionFailure) { + } 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,7 +70,7 @@ 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); } } @@ -124,8 +126,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(seenExceptions); } return seenExceptionsLocal; } @@ -144,32 +149,31 @@ final void clearSeenExceptions() { private abstract static class AtomicHelper { /** Atomic compare-and-set of the {@link AggregateFutureState#seenExceptions} field. */ 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. */ abstract int decrementAndGetRemainingCount(AggregateFutureState state); } private static final class SafeAtomicHelper extends AtomicHelper { - final AtomicReferenceFieldUpdater, Set> + final AtomicReferenceFieldUpdater< + ? super AggregateFutureState, ? super @Nullable Set> seenExceptionsUpdater; - final AtomicIntegerFieldUpdater> remainingCountUpdater; + final AtomicIntegerFieldUpdater> remainingCountUpdater; - @SuppressWarnings({"rawtypes", "unchecked"}) // Unavoidable with reflection API SafeAtomicHelper( - AtomicReferenceFieldUpdater seenExceptionsUpdater, - AtomicIntegerFieldUpdater remainingCountUpdater) { - this.seenExceptionsUpdater = - (AtomicReferenceFieldUpdater, Set>) - seenExceptionsUpdater; - this.remainingCountUpdater = - (AtomicIntegerFieldUpdater>) remainingCountUpdater; + AtomicReferenceFieldUpdater< + ? super AggregateFutureState, ? super @Nullable Set> + seenExceptionsUpdater, + AtomicIntegerFieldUpdater> remainingCountUpdater) { + this.seenExceptionsUpdater = seenExceptionsUpdater; + this.remainingCountUpdater = remainingCountUpdater; } @Override void compareAndSetSeenExceptions( - AggregateFutureState state, Set expect, Set update) { + AggregateFutureState state, @Nullable Set expect, Set update) { seenExceptionsUpdater.compareAndSet(state, expect, update); } @@ -182,7 +186,7 @@ int decrementAndGetRemainingCount(AggregateFutureState state) { 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; 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..c6d2a2723f96 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,10 @@ 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.io.Serializable; import java.util.concurrent.atomic.AtomicLong; /** @@ -49,7 +53,7 @@ * @author Martin Buchholz * @since 11.0 */ -public class AtomicDouble extends Number implements java.io.Serializable { +public class AtomicDouble extends Number implements Serializable { private static final long serialVersionUID = 0L; // We would use AtomicLongFieldUpdater, but it has issues on some Android devices. @@ -163,6 +167,7 @@ public final double getAndAdd(double delta) { * * @param delta the value to add * @return the updated value + * @since 31.1 */ @CanIgnoreReturnValue public final double addAndGet(double delta) { @@ -225,15 +230,14 @@ public double doubleValue() { * * @serialData The current value is emitted (a {@code double}). */ - private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeDouble(get()); } /** 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(); value = new AtomicLong(); set(s.readDouble()); diff --git a/android/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java b/android/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java index d55108a28168..0ccfcf9f2be4 100644 --- a/android/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java +++ b/android/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java @@ -17,8 +17,13 @@ import static java.lang.Double.longBitsToDouble; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.primitives.ImmutableLongArray; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; import java.util.concurrent.atomic.AtomicLongArray; /** @@ -44,7 +49,8 @@ * @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..3f97967be5ae 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,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.J2ktIncompatible; 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 +29,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 +44,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. @@ -54,6 +57,7 @@ * @since 11.0 */ @GwtCompatible +@J2ktIncompatible public final class AtomicLongMap implements Serializable { private final ConcurrentHashMap map; @@ -63,7 +67,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 +293,6 @@ boolean remove(K key, long value) { * * @since 20.0 */ - @Beta @CanIgnoreReturnValue public boolean removeIfZero(K key) { return remove(key, 0); @@ -325,7 +328,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..756237020362 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. @@ -34,13 +34,8 @@ 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..59408e2206d3 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,13 @@ 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.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; @@ -48,7 +48,6 @@ 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 @@ -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)") +@J2ktIncompatible // TODO(dpb): GWT compatibility. -public final class ClosingFuture { +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 @@ -232,10 +230,9 @@ public static final class DeferredCloser { * @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 +246,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 +264,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 +281,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 +300,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 +321,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 +339,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 +347,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 +366,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 +378,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,9 +407,32 @@ 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); } /** @@ -404,21 +442,21 @@ public static ClosingFuture submitAsync( * implements {@link Closeable}. 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 +468,16 @@ 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, final Executor closingExecutor) { checkNotNull(closingExecutor); final 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 +530,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 +545,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 +562,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 +585,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 +621,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); - } - - 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; + this(future, new CloseableList()); } - 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; } /** @@ -661,7 +672,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,7 +681,7 @@ public ListenableFuture statusFuture() { * one, or if this {@code ClosingFuture} has already been {@linkplain #finishToFuture() * finished} */ - public ClosingFuture transform( + public ClosingFuture transform( final ClosingFunction function, Executor executor) { checkNotNull(function); AsyncFunction applyFunction = @@ -712,7 +723,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)}. @@ -754,7 +765,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,7 +775,7 @@ public String toString() { * one, or if this {@code ClosingFuture} has already been {@linkplain #finishToFuture() * finished} */ - public ClosingFuture transformAsync( + public ClosingFuture transformAsync( final AsyncClosingFunction function, Executor executor) { checkNotNull(function); AsyncFunction applyFunction = @@ -793,7 +804,7 @@ public String toString() { * *

      *
    • It does not need to capture any {@link Closeable} objects by calling {@link - * DeferredCloser#eventuallyClose(Closeable, Executor)}. + * DeferredCloser#eventuallyClose(Object, Executor)}. *
    • It returns a {@link ListenableFuture}. *
    * @@ -808,8 +819,8 @@ public String toString() { * @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(final AsyncFunction function) { checkNotNull(function); return new AsyncClosingFunction() { @Override @@ -845,7 +856,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 @@ -910,7 +921,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)}. @@ -938,7 +949,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 @@ -995,13 +1006,13 @@ 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 @@ -1040,7 +1051,7 @@ 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 @@ -1078,7 +1089,7 @@ public void run() { executor); } - private static void provideValueAndCloser( + private static void provideValueAndCloser( ValueAndCloserConsumer consumer, ClosingFuture closingFuture) { consumer.accept(new ValueAndCloser(closingFuture)); } @@ -1100,8 +1111,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 +1122,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 +1162,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 +1183,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(); @@ -1209,9 +1221,7 @@ private FluentFuture callAsync( * .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 +1231,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 +1251,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 +1290,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( + public ClosingFuture call( final CombiningCallable combiningCallable, Executor executor) { Callable callable = new Callable() { @Override + @ParametricNullness public V call() throws Exception { return new Peeker(inputs).call(combiningCallable, closeables); } @@ -1326,9 +1337,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,7 +1346,7 @@ public String toString() { *

    The same warnings about doing heavyweight operations within {@link * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. */ - public ClosingFuture callAsync( + public ClosingFuture callAsync( final AsyncCombiningCallable combiningCallable, Executor executor) { AsyncCallable asyncCallable = new AsyncCallable() { @@ -1356,22 +1366,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 +1387,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 +1398,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 +1422,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 +1460,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( + public ClosingFuture call( final 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 +1504,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,7 +1513,7 @@ public String toString() { *

    The same warnings about doing heavyweight operations within {@link * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. */ - public ClosingFuture callAsync( + public ClosingFuture callAsync( final AsyncClosingFunction2 function, Executor executor) { return callAsync( new AsyncCombiningCallable() { @@ -1533,7 +1540,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 +1553,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 +1584,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 +1629,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( + public ClosingFuture call( final 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 +1677,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,7 +1686,7 @@ public String toString() { *

    The same warnings about doing heavyweight operations within {@link * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. */ - public ClosingFuture callAsync( + public ClosingFuture callAsync( final AsyncClosingFunction3 function, Executor executor) { return callAsync( new AsyncCombiningCallable() { @@ -1706,7 +1719,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 +1735,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 +1770,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 +1822,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( + public ClosingFuture call( final 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 +1871,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,7 +1880,7 @@ public String toString() { *

    The same warnings about doing heavyweight operations within {@link * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. */ - public ClosingFuture callAsync( + public ClosingFuture callAsync( final AsyncClosingFunction4 function, Executor executor) { return callAsync( new AsyncCombiningCallable() { @@ -1900,11 +1915,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 +1934,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 +1972,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 +2030,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( + public ClosingFuture call( final 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 +2081,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,7 +2090,7 @@ public String toString() { *

    The same warnings about doing heavyweight operations within {@link * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. */ - public ClosingFuture callAsync( + public ClosingFuture callAsync( final AsyncClosingFunction5 function, Executor executor) { return callAsync( new AsyncCombiningCallable() { @@ -2106,34 +2120,45 @@ 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(final @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) { + /* + * In guava-jre, any kind of Exception may be thrown because `closeable` has type + * `AutoCloseable`. + * + * In guava-android, the only kinds of Exception that may be thrown are + * RuntimeException and IOException because `closeable` has type `Closeable`—except + * that we have to account for sneaky checked exception. + */ + 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 +2177,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 + private static final class CloseableList extends IdentityHashMap implements Closeable { 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 +2196,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 +2222,7 @@ public void close() { } closed = true; } - for (Map.Entry entry : entrySet()) { + for (Map.Entry entry : entrySet()) { closeQuietly(entry.getKey(), entry.getValue()); } clear(); @@ -2202,7 +2231,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..3b32f0f0a47d 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,34 @@ 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 { +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; 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 +57,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 +66,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 +78,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 +91,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 +101,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..12de35eaff01 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,10 @@ package com.google.common.util.concurrent; 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.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 +27,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 +41,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 +158,7 @@ * @author Darick Tong * @since 13.0 */ -@Beta -@CanIgnoreReturnValue // TODO(cpovirk): Consider being more strict. +@J2ktIncompatible @GwtIncompatible public class CycleDetectingLockFactory { @@ -171,7 +169,6 @@ public class CycleDetectingLockFactory { * * @since 13.0 */ - @Beta public interface Policy { /** @@ -191,7 +188,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 +209,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 +277,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 +302,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) { @@ -392,7 +388,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 +415,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 +436,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; @@ -527,7 +527,6 @@ private static class ExampleStackTrace extends IllegalStateException { * * @since 13.0 */ - @Beta public static final class PotentialDeadlockException extends ExampleStackTrace { private final ExampleStackTrace conflictingStackTrace; @@ -549,7 +548,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 +563,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(); } @@ -677,8 +681,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 +712,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 + ArrayList acquiredLockList = requireNonNull(acquiredLocks.get()); LockGraphNode node = lock.getLockGraphNode(); node.checkAcquiredLocks(policy, acquiredLockList); acquiredLockList.add(node); @@ -723,7 +727,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 + ArrayList acquiredLockList = requireNonNull(acquiredLocks.get()); LockGraphNode node = lock.getLockGraphNode(); // Iterate in reverse because locks are usually locked/unlocked in a // LIFO order. 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..8ccd000b50f1 --- /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.errorprone.annotations.concurrent.GuardedBy; +import java.util.Collections; +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 Collections.emptyList(); + } + + @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..530595856d7b 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,7 @@ package com.google.common.util.concurrent; import com.google.common.annotations.GwtCompatible; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * {@link Error} variant of {@link java.util.concurrent.ExecutionException}. As with {@code @@ -29,21 +29,58 @@ */ @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); } 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..9b59eb05a46a 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,15 @@ 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.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 +40,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 +56,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 +68,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 +85,7 @@ * * @since 26.0 */ -@Beta +@J2ktIncompatible public final class ExecutionSequencer { private ExecutionSequencer() {} @@ -88,10 +96,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(); + private @LazyInit ThreadConfinedTaskQueue latestTaskQueue = new ThreadConfinedTaskQueue(); /** * This object is unsafely published, but avoids problematic races by relying exclusively on the @@ -122,11 +130,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 +146,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 +172,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 +203,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 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); } }; - // 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 +287,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 +294,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..a1a71c0e7f1e 100644 --- a/android/guava/src/com/google/common/util/concurrent/FluentFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/FluentFuture.java @@ -15,18 +15,22 @@ 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: @@ -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 { +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); } @@ -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) { @@ -242,12 +252,32 @@ 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 + @SuppressWarnings("Java7ApiChecker") + @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 +288,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( @@ -304,7 +335,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); } @@ -341,7 +372,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); } 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..5393dbdb615c 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,13 @@ package com.google.common.util.concurrent; +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}. */ +@J2ktIncompatible 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..7ff05340ebc0 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,13 @@ package com.google.common.util.concurrent; +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 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..f5b419f95469 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. @@ -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,51 @@ 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 + @SuppressWarnings("Java7ApiChecker") + @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; } @@ -272,9 +297,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, @@ -337,14 +362,14 @@ 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); } /** @@ -354,15 +379,35 @@ 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 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 + @SuppressWarnings("Java7ApiChecker") + @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); + } + + /** + * 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 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, @@ -405,12 +450,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); } /** @@ -442,9 +487,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,10 +513,9 @@ 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( + public static Future lazyTransform( final Future input, final Function function) { checkNotNull(input); checkNotNull(function); @@ -507,6 +551,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 +574,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 +601,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 +619,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 +633,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 +645,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 +658,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)); } /** @@ -637,10 +689,8 @@ public static FutureCombiner whenAllSucceed( * * @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 +713,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 +738,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 +760,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) { 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 +786,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,9 +797,9 @@ 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) { this.delegate = delegate; @@ -741,15 +809,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 +848,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 +884,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,8 +910,7 @@ 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); @@ -846,14 +923,7 @@ public static ImmutableList> inCompletionOrder( final 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()); + copy[i].addListener(() -> state.recordInputCompletion(delegates, localI), directExecutor()); } @SuppressWarnings("unchecked") @@ -863,7 +933,7 @@ 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; if (futures instanceof Collection) { @@ -877,8 +947,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 +959,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 +979,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 +994,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 +1022,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 +1043,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); } @@ -1011,7 +1096,7 @@ private void recordCompletion() { * @param executor The executor to run {@code callback} when the future completes. * @since 10.0 */ - public static void addCallback( + public static void addCallback( final ListenableFuture future, final FutureCallback callback, Executor executor) { @@ -1020,7 +1105,7 @@ public static void addCallback( } /** 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; @@ -1045,7 +1130,8 @@ public void run() { } 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 +1165,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 +1207,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 +1223,69 @@ 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 + @SuppressWarnings("Java7ApiChecker") + @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 +1329,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 +1374,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..4f65cbda5bb0 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,12 +99,6 @@ 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. @@ -109,35 +107,8 @@ static GetCheckedTypeValidator classValueValidator() { */ @VisibleForTesting static class GetCheckedTypeValidatorHolder { - static final String CLASS_VALUE_VALIDATOR_NAME = - GetCheckedTypeValidatorHolder.class.getName() + "$ClassValueValidator"; - 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 +155,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 +181,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 +190,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 +206,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..e21f556c266b 100644 --- a/android/guava/src/com/google/common/util/concurrent/GwtFluentFutureCatchingSpecialization.java +++ b/android/guava/src/com/google/common/util/concurrent/GwtFluentFutureCatchingSpecialization.java @@ -15,6 +15,8 @@ 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 @@ -22,7 +24,9 @@ * FluentFuture.catching} family of methods. Those versions have slightly different signatures. */ @GwtCompatible(emulated = true) -abstract class GwtFluentFutureCatchingSpecialization extends AbstractFuture { +@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..f6b210ca3466 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 @@ -23,6 +24,7 @@ * different signatures. */ @GwtCompatible(emulated = true) +@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..b7cababe8921 100644 --- a/android/guava/src/com/google/common/util/concurrent/IgnoreJRERequirement.java +++ b/android/guava/src/com/google/common/util/concurrent/IgnoreJRERequirement.java @@ -20,5 +20,10 @@ 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}) @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..782a20410920 --- /dev/null +++ b/android/guava/src/com/google/common/util/concurrent/Internal.java @@ -0,0 +1,46 @@ +/* + * 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 +@SuppressWarnings("Java7ApiChecker") +@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..9191436088ab 100644 --- a/android/guava/src/com/google/common/util/concurrent/InterruptibleTask.java +++ b/android/guava/src/com/google/common/util/concurrent/InterruptibleTask.java @@ -14,15 +14,17 @@ 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) @ReflectionSupport(value = ReflectionSupport.Level.FULL) // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause @@ -30,7 +32,8 @@ // 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 @@ -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 @@ -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..611c91e6bf20 100644 --- a/android/guava/src/com/google/common/util/concurrent/JdkFutureAdapters.java +++ b/android/guava/src/com/google/common/util/concurrent/JdkFutureAdapters.java @@ -17,23 +17,27 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; -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,9 +99,8 @@ 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 class ListenableFutureAdapter + extends ForwardingFuture implements ListenableFuture { private static final ThreadFactory threadFactory = new ThreadFactoryBuilder() @@ -147,22 +152,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..c3d84c6200f7 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: * *